이전 포스팅을 통해 강한 참조 순환이 생기는 이유와 해결 방법을 알아보았다. 특히 클로저와 클래스 인스턴스 사이에 생기는 참조 순환은 일상적으로 발견하기 쉬운데 self 를 캡처하는 모든 클로저가 메모리 누수를 일으키는 것은 아니다.
이번 포스팅을 통해 어떤 클로저가 실제 메모리 누수를 일으키는지, 따라서 [weak self] 를 작성해야 하는지 확인해보자.
non-escaping vs. escaping 클로저
클로저에는 크게 두 가지 분류가 있는데, 이름에서 알 수 있듯 어딘가로 탈출할 것 같은 클로저와 그렇지 않은 클로저이다.
non-escaping 클로저
어디 가지 않고 있을 것 같은 이름의 클로저 즉, 선언되는 즉시 실행된다. 프로퍼티에 저장되거나 추후에 실행되거나 하는 일이 없다.
컴파일러는 non-escaping 클로저가 선언된 procedure (혹은 function) 가 끝나는 (return) 순간에 클로저가 캡처한 모든 객체를 놓아줄 것을 보장한다.
즉, non-escaping 클로저는 그를 둘러싼 문맥이 종료되기 전에 마무리 됨이 확실하다는 것이다. (이 때문에 non-escaping 클로저 내부에서는 self 를 명시적으로 사용하지 않아도 된다.)
따라서, 이미 종료된 문맥에 클로저의 작업이 남아 있거나 하는 일이 없고 이는 참조 순환이 생기지 않는다는 뜻이다. 실제로 아래에서 소개할 특정 이유로 클로저의 종료를 지연시키는 몇 가지 경우를 빼면 non-escaping 클로저에는 [weak self] 가 필요없다.
Escaping closures
이름처럼 어딘가로 이동한다. 프로퍼티에 저장되거나 다른 클로저에 넘겨져 선언 이후 어느 시점에 실행된다.
이러한 클로저에서 self 키워드를 통해 클래스 인스턴스에 강한 참조를 가진다면 강한 참조 순환이 생길 수 있다. 이 경우는 [weak self], [unowned self] 를 통해 '강하지 않게 참조' 함으로써 메모리 누수를 예방해야 한다.
클로저의 할당 해제를 지연시키는 경우
non-escaping 클로저가 [weak self] 를 필요로 하지 않는 이유에 대해 알아봤는데, 어떤 이유로 클로저의 할당 해제를 지연하는 경우에는 [weak self] 를 사용해야 할 수 있다.
지연되긴 하지만 해제되긴 하므로 메모리 누수와는 조금 차이가 있지만 원하지 않는 결과를 가져올 수 있다는 점에서 참조 순환의 고리를 끊을 필요가 있다. 예를 들어, 지연되는 클로저 때문에 어떤 컨트롤러의 해제가 원하는 시점에 이루어지지 않을 수 있다.
클로저에서 네트워킹 다운로드 등의 시간이 걸리는 작업을 하거나, 의도적으로 delay 를 준 후 실행되거나, timeout 이 긴 callback 을 받는 등의 작업이 이 경우에 해당한다.
[weak self] and guard let self = self
[weak self] 는 self 를 옵셔널로 만들기 때문에 옵셔널 체이닝 혹은 guard let, if let 등을 통해 바인딩해야 사용할 수 있다. guard let self = self 를 사용하는 경우 클로저의 범위 내에서는 self 에 대한 강한 참조를 보장받는다.
즉, 어떠한 이유로 클로져의 할당 해제가 지연되더라도 guard let 으로 바인딩한 self 는 클로저의 범위 내에서 할당 해제되지 않을 것을 보장 받는다.
따라서, 컨트롤러의 할당 해제에 무관하게 클로저 내의 작업이 완료되어야 하는 경우 guard 구문을 사용하고 그렇지 않은 경우 프로퍼티에 접근할 때 self?.someProperty 로 nil 을 확인하는 것이 좋다.
References
You don't (always) need [weak self] | by Besher AI Maleh | Medium
'iOS 개발 > App 개발 관련' 카테고리의 다른 글
[iOS] Unit & UI Testing (0) | 2021.07.10 |
---|---|
[iOS] MVC 구조의 앱을 MVVM 으로 바꾸기 (0) | 2021.06.25 |
[iOS] 최고의 디자인 패턴, MVC 에 대한 오해 (0) | 2021.06.24 |
[iOS] 강한 참조 순환(Retain Cycle), 참조 순환 해결하기 (0) | 2021.04.12 |
[iOS] Architecture patterns(MVC,MVP,MVVM) (0) | 2020.08.09 |