클로저는 참조하는 변수를 캡처하여 살려 둡니다. 클로저가 self를 캡처하고 self도 그 클로저를 (저장 프로퍼티로) 보유하면, 둘이 서로를 영원히 살려 두게 됩니다. 이것이 메모리를 누수시키는 **순환 참조(retain cycle)**입니다. [weak self] 같은 캡처 리스트가 그 순환을 끊습니다.
ARC는 참조 카운트가 0이 되면 객체를 해제합니다. 강한 캡처는 그 카운트를 증가시킵니다.
final class Downloader {
var onDone: (() -> Void)? // 저장된 클로저 -> 강함
func start() {
// 나쁨: 클로저가 self를 강하게 캡처, self가 클로저 보유 -> 순환
onDone = { self.cleanup() }
// 좋음: 약한 캡처, self가 해제될 수 있음
onDone = { [weak self] in
guard let self else { return } // 안전하게 언래핑; 사라졌으면 종료
self.cleanup()
}
}
func cleanup() {}
}
[weak self] — self가 옵셔널이 됩니다. 객체가 해제되었으면 nil입니다. 객체가 클로저보다 오래 살지 못할 수 있을 때의 안전한 기본값입니다.[unowned self] — 옵셔널이 아니고 ARC 오버헤드가 없지만, self가 이미 사라졌으면 크래시가 납니다. self가 클로저보다 오래 산다고 보장될 때만 사용하세요.Task { [weak self] in
let data = await self?.fetch() // 해제되었으면 self?가 단락 평가
}
비탈출(non-escaping) 클로저(예: map, filter, forEach)는 동기적으로 실행되고 캡처를 즉시 해제합니다. 순환도 없고 [weak self]도 필요 없습니다. 위험은 탈출하며 저장되는 클로저에 있습니다. 완료 핸들러, Timer, NotificationCenter, Combine sink, delegate 블록 등입니다.
순환 참조는 iOS에서 가장 흔한 메모리 누수이며, Instruments가 증가하는 메모리를 보여주기 전까지는 보이지 않습니다. 클로저가 언제 탈출하고 왜 [weak self]가 필요한지 정확히 아는 것 — 반사적으로 어디에나 남발하는 것과 대조되는 — 이 ARC를 이해하는 엔지니어와 맹목적으로 따라 하는 엔지니어를 가릅니다.
주니어부터 시니어까지 상세한 답변이 포함된 IT 면접 질문 라이브러리.
후원하기