캡쳐값
- 클로저는 정의된 둘러싸인 컨텍스트에서 상수와 변수를 캡쳐할 수 있다. 그러면 클로저는 상수와 변수를 정의한 원래 범위가 더 이상 존재하지 않더라도 바디 안에서 해당 상수와 변수값을 참조하고 수정 가능하다. 스위프트에서 값을 캡처할 수 있는 가장 간단한 클로저는 중첩함수이다. 이는 바깥 함수의 어떠한 인자도 캡처 가능하며 바깥 함수 내에 정의된 상수와 변수의 캡처도 가능하다.
func makeIncrementer(forIncrement amount : Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement : 10)
incrementByTen()
incrementByTen()
incrementByTen()
let incrementBySeven = makeIncrementer(forIncrement : 7)
- 클래스 인스턴스의 프로퍼티에 클로저를 할당하고 클로저가 인스턴스 또는 멤버를 참조하여 해당 인스턴스를 캡쳐하면 클로저와 인스턴스 사이에 '강한 참조' 사이클이 생성된다. 스위프트는 캡처 목록을 통해 이런 강한 참조 사이클을 깨뜨린다.
클로저는 참조타입
- 위에서 보듯 incrementBySeven과 incrementByTen은 상수이지만 이러한 상수가 참조하는 클로저는 캡쳐한 runningTotal을 계속 증가시킬 수 있다. 이는 함수와 클로저가 참조타입이기 떄문이다. 함수 또는 클로저를 상수 및 변수에 할당할 떄마다 실제로 해당 상수 및 변수를 함수 또는 클로저에 대한 참조로 설정다면 서로 다른 2개의 상수라 할지라도 같은 클로저를 참조한다는 의미이다.
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
incrementByTen()
탈출 클로저(escaping closer)
- 클로저를 파라미터로 갖는 함수를 선언 시 이 클로저는 탈출을 허락한다는 의미로 파라미터 타입전 @escaping을 작성할 수 있다.
- 클로저가 탈출이 가능한 한가지 방법은 함수 바깥에 정의된 변수에 저장되는 것이다. 다음 예시는 비동기작업을 시작하는 함수인데 대부분 완료 핸들러로 클로저를 사용한다. 이 함수는 시작한 후 반환되지만, 작업이 완료될때까지 클로저가 호출되지 않는다. 클로저는 나중에 호출하려면 탈출해야 한다. 함수가 종료되면 클로저 호출이 안되기 떄문이다.
var completionHandler: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler : @escaping () -> Void) {
competionHandler.append(completionHandler)
}
func someFunctionWithNoneEscapingClosure(closure() -> Void) {
closure()
}
class someClass {
var a =10
func doSomething() {
someFunctionWithEscapingClosure {
[self].x = 100
}
someFunctionWithNoneEscapingClosure {
x = 200
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
completionHandlers.first?()
print(instance.x)
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNoneescapingClosure {x = 200}
someFunctionWithEscapingClosure {x = 100}
자동 클로저
- 자동 클로저는 함수에 인자로 전달되는 표현식을 래핑하기 위해 자동으로 생성되는 클로저이다.인자를 별도로 가지지 않고, 호출될 때 내부에 래핑된 표현식의 값을 반환한다. 자동클로저를 가지는 함수를 호출하는 건 일반적이지만 이러한 함수를 구현하는 건 일반적이지 않다.
var customerInLine = ["Chris", "Alex", "Ewa"]
print(customerInLine.count)
let customerProvider = { customerInLine.remove(at: 0) }
print(customerInLine.count)
print("Now! provide \(customerProvider())!")
print(customerProvider.count)
- 자동클로저는 @autoclosure 속성을 표기하여 자동 클로저를 가진다고 나타낼 수 있다. 그렇게 되면 위의 예시는 다음과 같이 다시 나타낼 수 있다.
func serve(customer customerProvider: @autoclosure () -> String) {
print("now! \(customerProvider())!")
}
- 자동클로저가 이스케이프를 허용하길 원하면 @autoclosure와 @escaping 클로저 속성을 둘다 사용하면 된다.
var customersInLine = ["Barry", "Daniella"]
var customerProvider: [() -> String] = []
func collectCustomerProvider(_ customerProvider : @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at:0))
collectCustomerProviders(customersInLine.remove(at:0))
print("collected \(customerProviders.count) closures.")
for customerProvider in customerProviders {
print(Now serving \(customerProvider())!")
}