Closure의 중첩 시 weak은 어디까지 self를 붙잡아(강한 참조) 두는가?
표현할 수 있는 적절한 말이 무엇인지는 잘 모르겠다.
Swift의 closure를 사용할 때 Capturing이 되면서 self(객체)의 강한 참조가 발생한다.
이때 강한 참조 인해 발생하는 문제들을 피하기 위해 weak을 사용하여 약한 참조 형태로 self에 접근하는 것이 일반적인 방법이다.
그렇다면 중첩된 closure는 weak의 위치에 따라 어떻게 동작할까?
만약 아래와 같은 코드를 작성했을 때 weak이 사용되는 위치에 따라 어떤 결과가 나오는지 확인해보려 한다.
Case 1. escaping Closure를 받는 메서드를 호출했을 때
위치 1을 보면 loadContentFromSource를 호출하는 첫 번째 클로저는 weak 키워드로 self를 약한 참조로 접근하게 해 준다.
여기서 '약한 참조'라는 것은 iOS의 ARC가 판단하에 메모리에서 해제시킬 수 있는 대상이 되는 것이다. ARC는 따로 정리를 해야겠다.
어쨌거나 실행을 하기 전에 weak 키워드를 쓰게 되면 이하 블록 안에서 사용되는 self는 옵셔널이게 된다.
위에서 말했듯이 weak을 사용하면 자동적으로 메모리에서 해제가 되는 것을 허용한다는 것이다. ARC는 RC가 0인 클래스 인스턴스를 메모리에서 해제하는데, loadContentFromSource를 호출하고 asyncAfter에 의해 3초 뒤 다시 돌아왔을 때 현재 객체가 nil이 되어 있을 수도 있기 때문에 optional의?를 붙여야 한다.
느낌표(!)를 붙이고 강제 언랩핑을 할 수 도 있지만 실험의 목적과는 상관이 없으므로 옵셔널체이닝 방식을 사용하겠다.
결과로그
called loadContent
ExampleData deinit
.
.(3초 대기)
.
loaded Fromsource content is CompletionString~
last async
분석
getExampleContent 함수의 내부에서 Example객체 생성된다. 동시에 객체의 RC는 1이 되었다.
그리고 loadContent를 호출하면서 ContentLoader.loadContentFromSource 를 호출하는데 이때 weak키워드로 현재 객체를 약한참조로 가져오게 되므로 loadContentFromSource 내부의 asyncQueue를 기다리지 않게 된다. 이에 Example객체는 getExampleContent 가 종료되면서 RC카운트가 0으로 되어 deinit이 불리게 된다.
Case 2. 내부 클로저에서의 약한 참조
결과로그
called loadContent
.
.(3초 대기)
.
loaded Fromsource content is CompletionString~
ExampleData deinit
last async
분석
case1과 달리 loadContentFromSource에서 3초의 딜레이를 기다리고 나머지 세 번의 로그가 출력되었다.
weak의 위치가 내부 클로저로 이동함에 따라 loadContentFromSource를 호출하는 클로저에서 참조하는 현재 객체가 강한 참조로 남아있는 상태로 보인다.
때문에 async 한 3초의 대기를 기다리게 되고 이후 실행되는 클로저에서 weak으로 약한 참조를 사용하자 곧바로 deinit이 불리게 되었다.
Case3. weak을 사용하지 않을 경우
아직까지도 Set Content로그가 출력되지 않았다.
self?.content = loadedContent 에서 self가 약한 참조이므로 메모리에서 해제되었기 때문이다.
content에 값을 넣는 부분까지 가려면 중첩된 내부 클로저까지 강한 참조를 갖고 있어야 한다.
코드에서 weak을 사용하지 않고 실행을 해보면 아래와 같은 결과가 출력된다.
결과로그
called loadContent
.
.(3초 대기)
.
loaded Fromsource content is CompletionString~
last async
Set Content
ExampleData deinit
분석
모든 코드가 동작할 때까지 self객체는 강한 참조로 붙잡혀 있게 된다. 그러므로 content변수에 값이 할당되고 나서 deinit이 불리게 된다.
결론
- weak 키워드를 사용함에 따라 객체의 수명주기가 달라짐을 확인할 수 있었다.
- 내부 클로저의 동작이 모두 끝날 때까지 현재 객체가 메모리에서 해제되지 않기를 원한다면 강한 참조로 사용하고,
비동기 방식의 결과 리턴이 현재 객체가 해제되었을 때 동작하지 않게 하려면 해당 위치에 weak을 사용한다.
- 강한 참조를 물고 있을 경우 클로저 내부의 코드가 동작이 완료될 때까지 self객체가 강하게 참조되고 있으므로 이를 유의하며 코딩해야 한다.
- 개발자의 의도에 따라서 weak의 위치를 적재적소에 사용하는 것이 좋다고 본다.
참고:
https://stackoverflow.com/questions/33264558/correct-placement-of-capture-list-in-nested-closures-in-swift
'난 iOS개발자 > iOS' 카테고리의 다른 글
UITextField LeftPadding (0) | 2021.07.29 |
---|---|
Equatable (0) | 2021.07.29 |
RTL (0) | 2021.07.29 |
개행(\n) 없이 두 줄 이상의 문자열 할당하기 (0) | 2021.07.29 |
String Extension (0) | 2021.07.27 |