배경
- escaping closure를 자주 사용하고 접하면서도, 정확히 어떤 상태일 때 escaping으로 동작하는지 잘 몰랐던 것 같아 정리하게 되었다.
escaping closure
- 클로저가 함수의 매개변수로 전달되었지만, 함수 반환 이후(함수의 생명주기 외부에서)에 호출되는 경우 escape(탈출)한다고 할 수있다.
- 이 때 인자 타입 이전에 @escaping을 작성해 해당 클로저는 탈출이 허용됨을 나타낼 수 있다.
상황 예시
- 클로저가 탈출할 수 있는 하나의 예시는 함수 바깥에 정의된 변수에 저장되어 있는 경우이다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
- 위 예제는 클로저를 매개변수로 받아 함수 외부에 선언된 배열에 추가한다.
- 추가된 클로저의 실행은 someFunctionWithEscapingClosure(_:) 함수가 종료된 이후에 일어나기 때문에, 해당 매개변수는 @escaping으로 표시되어야 한다.
- 객체의 생성자 매개변수로 클로저를 전달받아 객체의 클로저 프로퍼티를 초기화할 때 @escaping을 통해 탈출 가능한 클로저임을 나타내야 하는 이유도 위와 같음.
- 해당 클로저는 init 호출이 끝난 이후 호출될 것이기 때문!
- 또 다른 예시는 익히 알고 있는 비동기적인 작업 이후의 컴플리션 핸들러.
- 해당 핸들러의 호출이 원본 함수의 종료 이후에 이루어지기 때문에 @escaping으로 마크해줘야 함
self 캡처 주의
- self가 클래스 인스턴스라면, 탈출 클로저 내부에서 self에 참조하는 것은 주의해야 한다.
- 순환참조 발생 가능성이 있음.
- escaping 클로저에서 self를 캡처하고 싶으면 명시적으로 self를 사용해야 함
- self를 사용할 때 명시적으로 적어주거나, 캡처 리스트에 명시적으로 작성
- 이를 통해 escaping 클로저 사용 중 순환 참조는 없는지 한 번 더 확인할 수 있음.
- self가 구조체 또는 열거형의 인스턴스라면, 언제나 self에 암시적으로 참조 가능.
- 그러나 탈출 클로저는 self가 구조체 또는 열거형이라면 self의 변경 가능한 참조를 캡처할 수 없다.
- 구조체와 열거형은 공유 가능한 변경성을 허용하지 않기 때문.
정리
- 함수의 파라미터로 클로저를 받았을 때, 해당 클로저가 함수의 생명주기 외부에서 호출 또는 실행된다면, 그 클로저는 escaping 클로저!
- 탈출 클로저는 함수 흐름을 벗어나 실행될 수 있기 때문에, 클래스 인스턴스 self를 캡처할 경우 조심
- 클로저가 클래스 인스턴스를 self로 참조하고, 클래스도 클로저를 참조하고 있다면 순환 참조 발생 가능
- 따라서 캡처 리스트에서 weak 등의 키워드를 통해 관리 필요
출처
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/