질문 배경:
Escape 분석은 컴파일러 최적화와 메모리 관리의 용어입니다. 스위프트에서는 클로저와 ARC(자동 참조 계수)를 적극적으로 사용하기 때문에 중요합니다. 이는 클로저가 함수의 가시성 범위를 넘을 수 있는지를 정의하는 escaping 및 non-escaping 클로저와 관련이 있습니다.
문제:
클로저 유형을 잘못 정의하면 메모리 소유권 오류, 메모리 누수, 변수의 의도치 않은 캡처 및 성능 저하가 발생할 수 있습니다. 클로저가 "나가는"(escapes) 경우와 그렇지 않은 경우를 명확하게 이해하고 이를 올바르게 표시해야 합니다.
해결 방법:
스위프트는 escaping 클로저를 @escaping 속성으로 명시적으로 표시해야 합니다. Non-escaping 클로저는 함수 내에서만 캡처될 수 있으며, 자동으로 더 효율적인 메모리 관리 기능을 가지며 캡처된 변수를 더 안전하게 사용할 수 있습니다.
차이점의 예:
// non-escaping 클로저 (더 빠르며 호출 후 저장되지 않음) func performSync(block: () -> Void) { block() } // escaping 클로저 (저장되어 나중에 실행될 수 있음) var storedCompletion: (() -> Void)? func performAsync(block: @escaping () -> Void) { storedCompletion = block }
주요 특징:
non-escaping으로 정의된 클로저를 함수 코드 변경 없이 escaping으로 변경할 수 있습니까?
아니요. @escaping 속성은 함수 시그니처에서 명시적으로 지정되어야 합니다. 함수 내의 클로저가 그 범위를 넘겨지는 경우, 반드시 escaping 클로저가 필요하며, 그렇지 않으면 컴파일러가 오류를 발생시킵니다.
weak/unowned 캡처 없이 self를 escaping 클로저로 전달하는 것이 안전합니까?
아니요. Escaping 클로저는 self를 강한 참조로 캡처하는 경우 retain cycle을 유발할 수 있습니다. 캡처를 명확하게 관리해야 합니다:
someAsync { [weak self] in self?.doSomething() }
항상 escaping 클로저는 글로벌이며(프로그램이 종료될 때까지 메모리에 유지됨)입니까?
아니요. 그 생명 주기는 저장 영역에 따라 다릅니다. 클로저가 임시로만 저장되거나 속성이 null로 설정되면, 속성이 소유자를 잃을 때 즉시 해제됩니다. 글로벌 객체에 대한 참조나 이를 글로벌 변수에 저장하는 클로저만이 글로벌로 간주됩니다.
클래스 메소드 내에서 @escaping 없이 클로저를 저장 (컴파일러 오류), 나중에 수정하고 retain cycle 방지 조치를 잊어버림 — 애플리케이션에서 메모리 누수 발생.
장점:
단점:
개발자는 항상 escaping 클로저가 필요한 위치를 확인하고, self를 weak/unowned로 캡처하여 누수를 방지하며 안전하게 메모리를 관리합니다.
장점:
단점: