iOS/Swift
[Swift] 클로저 (2) - Capturing Values, Escaping Closure, Auto Closure
겸둥이xz
2021. 6. 21. 22:05
반응형
클로저의 값 캡쳐(Capturing Values)
- 클로저는 참조 타입(Reference Type)입니다.
- 클로저가 매개변수나 지역변수가 아닌 주변 외부의 context를 사용하기 위해 주변 외부의 context를 참조하는 것
- 원본 값이 사려져도 클로저의 body 안에서 그 값을 활용할 수 있습니다.
예제 코드
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
let incrementBySeven = makeIncrementer(forIncrement: 7)
// runnigTotal과 amount가 캡쳐되서 그 변수를 공유하기 때문에 누적된 결과를 가집니다.
let plusedTen = incrementByTen() // 10
let plusedTen2 = incrementByTen() // 20
// 다른 클로저이기 때문에 고유의 저장소에 runningTotal과 amount를 캡쳐해서 사용합니다.
let plusedSeven = incrementBySeven() // 7
let plusedSeven2 = incrementBySeven() // 14
- makeIncrementer 함수는 Int 타입 매개변수를 갖고, () -> Int 타입의 클로저를 반환합니다.
- makeIncrementer 내부에는 incrementer라는 함수가 클로저로 반환되고 있습니다.
- incrementer는 runningTotal과 amount를 캡쳐해서 사용하고 있습니다.
한 클로저를 두 상수나 변수에 할당하면 두 상수 또는 변수는 같은 클로저를 참조하게 됩니다.
이렇게 Capturing by reference 하면 makeIncrementer에 대한 호출이 종료될 때 runningTotal과 amount가 사라지지 않으며 다음에 Incrementer 함수가 호출될 때 runningTotal을 사용할 수 있습니다.
만약 클로저를 어떤 클래스 인스턴스 프토퍼티로 할당하고, 그 클로저가 해당 인스턴스를 캡쳐하면 강한 순환 참조(Strong Reference Cycles)에 빠지게 됩니다. 인스턴스의 사용이 끝나도 메모리를 해제하지 못하게 되는 현상입니다. 그래서 Swift는 이 문제를 다루기 위해서 캡쳐 리스트를 사용합니다.
Escaping Closure
- 클로저가 함수의 인자로 전달되지만 함수 밖에서 실행되는 것(함수가 반환된 후 실행됨)을 Escaping이라고 합니다.
- 이러한 경우 매개변수 타입 앞에 @escaping이라는 키워드를 명시해야 합니다.
- 비동기로 실행되거나 completionHandler(완료에 따른 처리)로 사용되는 클로저의 경우 자주 사용됩니다.
클로저의 Escaping은 하나의 함수가 마무리된 상태에서만 다른 함수가 실행되도록 함수를 작성할 수 있습니다.
따라서, 이를 활용해서 함수 사이에 실행 순서를 정할 수 있습니다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure() // 함수 안에서 끝나는 클로저
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줍니다.
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x) // 200
completionHandlers.first?()
print(instance.x) // 100
- somFunctionWithEscapingClosure에서 인자로 전달된 completionHandler는 함수가 끝나고 나중에 처리됩니다.
- 만약 함수가 끝나고 실행되는 클로저에 @escaping 키워드를 붙이지 않으면 컴파일 오류가 발생합니다.
- @escaping을 사용하는 클로저에서는 self를 명시적으로 언급해야 합니다.
Auto Closure
- 자동 클로저는 인자 값이 없으며 특정 표현을 감싸서 다른 함수에 전달 인자로 사용할 수 있는 클로저입니다.
- 자동 클로저는 클로저를 실행하기 전까지 실제 실행이 되지 않습니다.
- 실제 계산이 필요할 때 호출이 되기 때문에 계산이 복잡한 연산을 하는데 유용합니다.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count) // 5
let customerProvider = { customersInLine.remove(at: 0) } // 해당 코드가 지나도 count가 줄지 않습니다. Auto Closure
print(customersInLine.count) // 5
// customerProvider가 실행되었을때만 동작 (실제 실행)
print("Now serving \(customerProvider())!") // "Now serving Chris!"
print(customersInLine.count) // 4
// customersInLine : ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
// 자동 클로저를 함수의 인자 값으로 사용
serve(customer: { customersInLine.remove(at: 0) } ) // "Now serving Alex!"
@autoclosure 키워드를 이용해서 간결하게 표현 가능합니다.
// customersInLine : ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0)) // "Now serving Ewa!"
<참고자료>
https://medium.com/@jgj455/%EC%98%A4%EB%8A%98%EC%9D%98-swift-%EC%83%81%EC%8B%9D-closure-aa401f76b7ce
반응형