Swift에서 클로저는 주변 컨텍스트에서 값과 참조를 "캡처"할 수 있습니다. 캡처된 값이 값 타입(구조체, 열거형)인 경우 클로저는 데이터를 복사합니다. 참조(예: 클래스)를 캡처할 경우 클로저는 클래스 인스턴스를 가리키는 참조를 저장하며, 이로 인해 [weak self] 또는 [unowned self]를 사용하지 않으면 retain cycle이 발생할 수 있습니다.
캡처 메커니즘은 비동기 작업 및 콜백을 구현할 수 있게 해주지만, 잘못 사용하면 메모리 관리에 문제가 발생합니다.
예제:
class MyClass { var value = 10 func doSomething() { let closure = { [weak self] in print(self?.value ?? "nil") } closure() } }
이 예제에서는 비동기 작업이 수행되므로 강한 참조 순환을 피하기 위해 [weak self]가 사용됩니다.
질문: "클로저에서 self를 캡처할 때 항상 [weak self]를 사용하는 것이 메모리 누수를 방지할 수 있습니까?"
답변: 아닙니다, 클로저 내에 강한 참조가 나타나면(예를 들어, 중간 변수 통해) [weak self]가 있더라도 retain cycle이 발생할 수 있습니다. 예를 들어:
let closure = { [weak self] in let strongSelf = self strongSelf?.doSomething() }
장기 지속 객체(예: NotificationCenter)에 클로저를 바인딩하면 self에 대한 다른 참조가 있으면 retain cycle이 발생할 수 있습니다.
이야기 1
iOS 애플리케이션 프로젝트에서 개발자가 네트워크 요청에 클로저를 전달할 때
[weak self]를 지정하는 것을 잊었습니다. 그 결과 요청이 실행되는 동안 뷰 컨트롤러 객체는 사용자가 화면을 닫아도 해제되지 않았습니다. 이는 네트워크 작업의 집중적인 사용 중 메모리 누수로 이어졌습니다.
이야기 2
타이머에 대한 복잡한 구독 시스템에서,
[weak]를 사용하지 않고 클로저를 통해 구독한 객체는 화면이 메모리에서 삭제된 후에도 계속 행동을 수행했습니다. 이는 이미 계층에서 없어진 UI 요소를 반복적으로 호출하게 되어 애플리케이션이 크래시되는 원인이 되었습니다.
이야기 3
데이터 캐싱을 구현할 때 클로저가 긴 생명 객체의 self에 대한 참조를
[unowned self]주석 없이 캡처했습니다. 원래 객체가 파괴된 후 클로저에서 self에 접근하려고 하면 이미 해제된 메모리에 접근하게 되어 애플리케이션이 크래시되었습니다.