[Effective Objective-C] #33 weak 참조를 사용하여 리테인 순환을 피하라
출처 : Effective Objective-C
-
객체들끼리 서로를 어떠한 방법으로 참조하여 순환을 이루고 있는 객체 그래프(object graph)에서 순환이 발생하는 것을 흔히 볼 수 있다.
순환이 발생하면 특정 지점에서 메모리 누수가 발생할 것이다.
순환을 이루는 모든 객체가 결국엔 순환 밖에 있는 객체들에 의해 참조되지 않기 때문이다.
그렇게 되면 순환 내의 객체에 접근할 수 있는 방법이 더는 없게 된다.
그뿐 아니라 순환 내의 객체들은 서로를 할당 해제하지 않을 것이다.
서로가 서로를 살아 있게 유지하기 때문이다.
-
리테인 순환 중 가장 간단한 것은 객체 두 개가 서로를 참조하는 것이다.
-
리테인 순환 내의 객체에 대한 마지막 참조가 사라지면 리테인 순환 전체가 누수가 된다.
이는 리테인 순환 내의 모든 객체에 더는 접근할 수 없음을 뜻한다.
-
맥 OS X 의 오브젝티브-C 앱은 가비지 컬렉터를 사용할 수 있었다.
가비지 컬렉터는 더 이상 접근할 수 없는 순환을 발견하면 순환 내의 모든 객체를 스스로 할당 해제한다.
그러나 맥 OS X 10.8 부터 공식적으로 가비지 컬렉터 지원이 중단되었다.
그리고 iOS 에는 가비지 컬렉터가 없다.
그 때문에 코드를 작성할 때 리테인 순환 문제를 꼭 알고 있어야 한다.
그리고 절대로 발생하게 해선 안 된다.
-
리테인 순환을 피하는 가장 좋은 방법은 weak 참조를 사용하는 것이다.
또한 이러한 참조는 비소유 관계를 표현할 때 사용할 수 있다.
이는 unsafe_unretained 프로퍼티 속성을 사용해서도 할 수 있다.
-
unsafe_unretained 속성은 프로퍼티의 값이 안전하지 않을 수 있고 인스턴스가 이 값을 리테인하지 않는다는 것을 가리킨다.
할당 해제된 프로퍼티 객체의 메서드를 호출하면 앱 크래시가 발생할 수 있다.
이 객체가 프로퍼티의 객체를 리테인하지 않았고 프로퍼티의 객체가 할당 해제되었을 수 있기 때문이다.
-
unsafe_unretained 프로퍼티 속성은 문법적으로는 assign 속성과 똑같다.
그러나 assign 은 보통 내장 타입(int, float, struct 등)에서만 사용된다.
unsafe_unretained 는 객체 타입에 주로 사용된다.
이 속성은 프로퍼티 값이 안전하지 않을 수 있다는 사실을 명확하게 나타낸다.
-
오브젝티브-C 런타임에 ARC 와 함께 생긴 새로운 기능 중 하나는 안전한 weak 참조를 만들 수 있는 기능이다.
이 weak 는 새로운 프로퍼티 속성인데 unsafe_unretained 와 완전히 동일하게 동작한다.
그러나 이 속성은 프로퍼티의 객체가 할당 해제될 때 즉시 프로퍼티 값을 nil 로 자동으로 설정해준다.
-
참조가 제거되었을 때 unsafe_unretained 속성은 금방 할당 해제될 인스턴스를 여전히 가리키지만
weak 속성을 사용하면 프로퍼티는 곧바로 nil 을 가리키게 된다.
-
그러나 weak 속성을 사용하면 지연이 허용되지 않는다.
weak 을 사용하면 코드를 안전하게 만든다.
앱이 크래시되는 대신 부정확한 데이터를 보여줄 것이다.
부정확한 데이터를 보여주는 것이 크래시되는 것보다 더 나은 사용자 경험을 줄 것이다. ( 옮긴이 주 : 글쎄..? )
그러나 weak 참조된 객체가 너무 일찍 제거되는 것은 여전히 버그다.
-
객체를 소유하지 않는다면 리테인하지 않는 것이 일반적인 규칙이다.
이 규칙의 유일한 예외는 컬렉션이다.
컬렉션은 구성 요소를 직접 소유하지 않지만 그 구성 요소 객체들이 컬렉션을 소유하는 대신 컬렉션이 그것들을 리테인한다.
객체가 소유하지 않는 무언가의 참조를 가지는 예는 델리게이트 패턴이다.
기억할 점
참조를 weak 로 만들면 리테인 순환을 피할 수 있다.
weak 참조는 자동으로 nil 로 채워질 수도, 채워지지 않을 수도 있다. ( zeroing pointer 라고 부르며 iOS 5.0 부터 그런 것은 제거되었다. )
자동으로 nil 로 설정되는 것은 ARC 에서 소개된 새로운 기능이다.
그리고 런타임에서 구현되었다.
자동으로 nil 로 채워지는 weak 참조를 읽는 것은 항상 안전하다. ( 반드시 정상 객체가 아니면 nil 이기 때문이다. )
그 말은 절대로 할당 해제되는 참조를 포함하지 않는다는 것이다.
댓글