Closures #5
Closures Are Reference Types
- 앞선 예에서 incrementBySeven과 incrementByTen이 상수임에도 불구하고 runningTotal 변수를 증가시킬 수 있는 것은 함수들이나 closure들이 참조 Type이기 때문이다.
- 함수나 closure를 상수나 변수에 할당하면 실제로는 그 상수나 변수는 함수나 closure를 참조하게 된다.
- 결국 하나의 closure를 서로 다른 상수나 변수에 할당하더라도 할당된 두 상수나 변수는 같은 closure를 참조하게 되는 것이다.
let alsoIncreamentByTen = incrementByTen
alsoIncreamentByTen()
// 50을 리턴
alsoIncreamentByTen()
// 60을 리턴
Escaping Closures (탈출 Closures)
- closure가 함수의 인자로 전달되었으나 함수가 결괏값을 리턴한 후(종료된 후) 호출되는 경우, 함수를 탈출했다고 표현한다.
- 함수 선언 시 closure 파라미터 타입 앞에 @escaping을 적어 탈출이 허용됨을 표시한다.
- closure가 탈출을 가능하게 하는 한가지 방법은 함수 외부에서 선언된 변수에 저장시키는 방법이다.
- 탈출 Closures는 비동기 방식으로 실행되는 함수의 completion handler로 주로 사용된다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
// 파라미터로 전달된 closure를 외부에서 선언된 completionHandlers 추가한다.
// 위 파라미터 선언부에서 @escaping을 빼면 컴파일 에러가 발생한다.
// Converting non-escaping parameter 'completionHandler' to
// generic parameter 'Element' may allow it to escape
completionHandlers.append(completionHandler)
}
- self가 클래스 인스턴스를 참조하는 경우 이 self를 참조하는 탈출 closure는 특별히 고려할 사항이 있는데, 탈출 closure 내에서 self를 capture 하는 경우 예기치 않게 강력한 참조 싸이클을 만들기가 쉽다.
- 보통 closure는 그 변수를 사용함으로써 암묵적으로 capture를 하게 되는데, self를 참조하고자 하는 경우 명시적으로 self를 적어주거나 closure의 capture list에 self를 포함해야 한다.
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
// 명시적으로 self를 사용하는 방법
class SomeClass {
var x = 10
func doSomething() {
// 명시적으로 self를 사용
someFunctionWithEscapingClosure { self.x = 100 }
// self를 사용하지 않음
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 200이 출력됨
completionHandlers.first?()
print(instance.x)
// 100이 출력됨
// capture list에 self를 포함시키고 암묵적으로 참조하는 방법
class SomeOtherClass {
var x = 10
func doSomething() {
// capture list에 self를 포함
someFunctionWithEscapingClosure { [self] in x = 100 }
// self를 사용하지 않음
someFunctionWithNonescapingClosure { x = 200 }
}
}
- 만일 self가 structure나 enumeration의 인스턴스라면 항상 self를 암묵적으로 참조하게 된다.
- 하지만 이 경우 탈출 closure에서는 self에 대해 변경 가능한 참조를 할 수 없다.
struct SomeStruct {
var x = 10
mutating func doSomething() {
// Error: "Escaping closure captures mutating 'self' parameter"
// mutating 함수 내에서 참조되었기 때문에 self는 변경 가능하다. 따라서
// 탈출 closure가 struct의 변경 가능한 self를 참조할 수 없다는 규칙에 따라
// 에러가 발생을 한다.
someFunctionWithEscapingClosure { x = 100 }
// Ok
someFunctionWithNonescapingClosure { x = 200 }
}
}