들어가며
저번 글에 이어서..원래는 바로 자주쓰이는 고차함수의 종류에 대해서 알아보려 했는데, 그 전에 Escaping Closure에 대해서 먼저 공부하려고 합니다! 클로저의 개념에 대해 아직 모르신다면 위 링크를 먼저 방문하시고 읽는것을 추천드립니다 :)
Escape는 다들 아시다시피 "탈출하다."라는 뜻입니다. 마찬가지로 Escaping Closure(@escaping) 역시 그 의미를 따라서 "함수가 끝난 뒤에 실행되는 클로저"입니다.
좀 더 직관적인 이해를 위해 Non-Escaping Closure와 Escaping Closure를 비교하면서 알아보겠습니다!
Non-Escaping Closure
func runClosure(closure: () -> Void) {
closure()
}
- 클로저가 runClosure()함수의 closure 인자로 전달됨
- 함수 안에서 closure()가 실행됨
- runClosure() 함수가 값을 반환하고 종료됨
Escaping Closure(@escaping)
class ViewModel {
var completionhandler: (() -> Void)? = nil
func fetchData(completion: @escaping () -> Void) {
completionhandler = completion
}
}
- 클로저가 fetchData() 함수의 completion 인자로 전달됨
- 클로저 completion이 completionhandler 변수에 저장됨
- fetchData() 함수가 값을 반환하고 종료됨
- 클로저 completion은 아직 실행되지 않음
...
조금 이해가 되시나요?
...
보다 명시적인 예시
class Myclass {
var x = 10
func callFunc() {
withEscaping { self.x = 100 }
withoutEscaping { x = 200 }
}
var completionHandlers: [() -> Void] = []
func withEscaping(completion: @escaping () -> Void) {
completionHandler.append(completion)
}
func withoutEscaping(completion: () -> Void) {
completion()
}
}
let mc = MyClass()
mc.callFunc() // 1.
print(mc.x) // 2. result:200
mc.completionHandlers.first?()
print(mc.x) // 3. result:100
- 클래스 인스턴스를 생성하여 callFunc() 호출
- @escaping로 명시해주지 않았던 withoutEscaping 메소드가 클로저를 바로 호출하므로 200 출력
- 이때 @escaping으로 명시해둔 메서드에서는 클로저를 completionHandler에 append하여 저장
- completionHandler 배열에 저장해둔 가장 앞에 있는 클로저를 호출하여 그제서야 100 출력
이처럼 탈출 클로저를 사용하면 클로저를 함수 구문 밖에서도 호출 할 수 있다는 장점이 있습니다!!
탈출 클로저를 구현하기 위해서는 꼭 @escaping으로 명시해주어야 합니다. 하지만 명시해두고 Non-Escaping 클로저를 사용해도 상관 은 없습니다..
그러면..
그냥 @escaping선언을 항상 default로 해두면 둘 다 사용할 수 있으니까 그렇게 쓰면 안되나???
굳이 Non-Escaping, Escaping으로 나누는 이유
바로 컴파일러의 퍼포먼스와 최적화 때문입니다.
Non-Escaping 클로저의 경우, 컴파일러가 클로저의 실행 종료 시점이 언제인지 알기 때문에 때에 따라 특정 객체에 대한 retain, release의 처리를 생략하여 life-cycle을 효율적으로 관리할 수 있습니다.(retain, release에 대해서는 이 글을 참고해주세요!)
하지만 Escaping 클로저는 함수의 밖에서 실행 되기 때문에 이를 보장하기 위해 클로저에서 사용하는 객체에 대한 추가적인 참조싸이클(reference cycles) 관리를 해주어야 합니다.
이러한 이유로 Swift에서는 필요시에만 Escaping클로저를 사용하도록 구분되어 있습니다!