해당 글은 http://mac-ios-web.tistory.com/ 발췌 되었습니다.
ARC (Automatic Reference Counting, 자동 참조 카운트)라는 새로운 기술이 나오면서 개발자가 더 이상 메모리 관리를 할 필요성이 없어졌다. 그런데 메모리 관리를 할 필요성이 사라졌다는 것은 메모리 관리에 대해 알 필요가 없다는 것을 꼭 의미하지는 않는 것 같다. 기존보다 좀 더 쉬워졌지만 그 내용을 알지 못하면 어느 순간 서로가 서로를 참조하는 유지 사이클이 발생하게 되고 이는 ARC를 사용함에도 불구하고 Memory Leak (메모리 누출)으로 이어진다. 이 문제를 방지하려면 먼저 프로퍼티의 속성인 strong (강한 참조)과 weak(약한 참조)에 대해 이해할 필요성이 있다.
처음에는 strong과 weak라는 용어를 봤을 때 도대체 무슨 차이가 있는지 알지 못했다. iOS 5.0때부터 공부하고 있기 때문에 기존에 쓰던 retain, release, assign이라는 프로퍼티 속성을 정확히 알지 못했고, retain과 assign이 각각 strong과 weak의 개념으로 바뀌었다는 것도 책을 통해서만 알게 되었다.
strong, weak의 개념 이해를 위해 한번 시작해보자. 참고로 ARC 기술은 개발자 대신 컴파일시 객체의 참조 카운트를 줄여주는 코드를 삽입한다. 만약 참조 카운트가 0이 되면 시스템이 자동으로 메모리에서 그 객체를 제거한다. 이 글의 목적은 그런 편리한 해제 코드가 안정적으로 돌아가기 위해서 서로가 서로를 참조하고 있어 메모리에 계속 남아있는 상태인, 유지 사이클을 방지하고자 하기 위함이다.
객체를 생성하면 그 객체는 막 태어났으므로 어느 시점 죽을 때까지는 살아있다. 즉, 객체 참조 카운트가 1로 시작한다. 그런데 그 객체를 여기 저기 필요에 의해 사용할 수 있다. 만약 n 군데서 그 객체를 안정적으로 사용하기를 원한다면 그 객체의 참조 카운트는 n개여야 한다. 그렇지 않고 원본 객체 그대로 사용한다면, 즉 객체의 참조 카운트가 1인 상태로 계속 사용된다면 다음과 같은 일이 발생할 수 있다. 누군가 자기만의 영역에서 그 객체를 유용하게 사용하고 있는데 다른 곳에서 그걸 알지 못하고 그 객체를 nil로 세팅해 참조 카운트를 강제로 감소시켜버린다. 그러면 어느 순간 그 객체는 갈 곳 잃은, 집도 없는 댕글링 포인터가 될 수 있다. 참 위험하다.
이를 방지하고자 strong 속성을 프로퍼티에 설정한다. 즉, 생성된 객체의 값이 다른 객체의 프로퍼티에 대입이 될 때, 그 프로퍼티의 속성이 strong이라면 대입되는 값을 소유하고자 하는 의도가 있는 것으로 판단되어, 그 객체의 참조 카운트를 하나 늘린다. 그리고 그 프로퍼티에서 그 객체를 사용을 다 하게 되면 그 객체의 참조 카운트는 감소한다. (ARC가 그런 해제 코드를 자동으로 넣는다.) 즉, 다른 곳에서 그 객체를 계속 사용하는데는 아무 문제 없다. 이것을 'strong', '강한 참조'라고 한다. (이는 기존의 retain이라는 의미와 비슷하다.)
weak (약한 참조)는 좀 의미가 다르다. 프로퍼티에 weak라고 설정이 되어있으면, 그 프로퍼티에 대입되는 객체는 가치를 인정받지 못해(?) 참조 카운트도 늘어나지 않는다. 말그대로 객체 원본 그대로 사용하는 것이다. 이는 strong과 다르게 심각한 댕글링 포인터 위험을 초래할 수 있다. 즉, 나는 이 곳에서 객체 A를 사용하고 있는데 누군가가 다른 곳에서 그 객체 A를 nil로 세팅해 참조 카운트를 감소시켜버렸다. 원래 참조 카운트가 1밖에 되지 않았기 때문에 이는 곧 메모리에서 그 객체가 제거됨을 의미한다. 그래서 나는 객체 A에 접근하는 순간 힙에서 데이터가 사라졌으므로 갑자기 BAD ACCESS가 발생하는 모습을 지켜봐야만한다. 그러나 weak에는 한가지 특성이 더 있다. 대상 객체가 힙에서 제거되면, 그 값을 가지고 있던 weak 속성의 프로퍼티도 같이 nil로 세팅된다. (Objective-C에서는 nil에 메시지를 보내도 예외가 발생하지 않는다.)
이는 weak만의 특징이며, assign과 구별되는 점이다. assign은 weak처럼 참조 카운트를 증가시키지는 않지만, 대상 객체가 사라졌을 때에도 여전히 기존의 포인터 주소를 가지고 있어 더 위험하다.
결론적으로 이 시나리오만 보면 weak가 strong보다는 좋지 않은 속성처럼 보인다. 그러나 이 속성은 유지 사이클을 방지하고 ARC를 정확하게 동작시키기에 중요하다.
다음과 같은 클래스가 있다고 하자.
@interface Item : NSObject {
@property (nonatomic, strong) Item* value1;
@property (nonatomic, strong) Item* value2;
}
그리고 아래처럼 객체 두개를 생성하고 A1의 프로퍼티에 생성된 객체 중 하나를 대입한다.
+ (void) someMethod () {
Item* A1 = [[Item alloc] init];
Item* A2 = [[Item alloc] init];
A1.value2 = A2;
}
A1, A2의 참조 카운트는 각각 1로 시작했다. 그런데 A1의 value1의 프로퍼티 속성은 strong이므로 A2의 참조 카운트를 하나 늘렸다. 결과적으로 A1의 참조 카운트는 1, A2의 참조 카운트는 2이다.
ARC는 컴파일 시 자동으로 해제 코드를 넣으므로, 메소드가 끝날 때 A1, A2의 참조 카운트는 각각 0, 1이 된다. A1의 참조 카운트는 0이 되어 시스템이 A1을 힙에서 제거한다. 그러면서 A1에 정의된 프로퍼티에 대해서도 각각 참조 카운트를 1씩 줄인다. 결국 A1의 value2의 참조 카운트는 1이 줄어들게 되고, 이는 A2의 참조 카운트가 0이 됨을 의미하기 때문에 A2도 역시 힙에서 제거된다.
여기까지는 괜찮다. 그런데 만약 다음처럼 A2가 A1을 갖고 있게 된다면 어떻게 될까?
+ (void) someMethod () {
Item* A1 = [[Item alloc] init];
Item* A2 = [[Item alloc] init];
A1.value2 = A2;
A2.value1 = A1;
}
A1, A2 각각 객체 생성시 참조 카운트를 1씩 가지고 있었다. 그런데 자신을 다른 곳의 프로퍼티에 각각 대입했다. 그것도 strong 속성의 프로퍼티. 그래서 A1, A2의 참조 카운트는 각각 2가 되었다.
메소드의 마지막에 도달하면 ARC에 의해 A1, A2의 참조 카운트가 1씩 줄어든다. 그러나 1씩 줄어도 여전히 A1, A2의 참조 카운트는 1씩 남아있다. 즉, 이것이 유지 사이클로 인한 메모리 릭이다.
<유지 사이클로 인한 메모리 누출. Leaks의 빨간 막대 표시가 메모리 누출을 의미.>
이럴 때 참조 카운트를 늘리지 않는 weak 속성이 필요하다. 프로퍼티 중 하나를 weak로 변경한다.
@interface Item : NSObject {
@property (nonatomic, weak) Item* value1;
@property (nonatomic, strong) Item* value2;
}
그리고나서 다시 생각한다.
+ (void) someMethod () {
Item* A1 = [[Item alloc] init];
Item* A2 = [[Item alloc] init];
A1.value2 = A2;
A2.value1 = A1;
}
메소드가 끝나기 전 A1의 최종 참조 카운트는 원래 그대로 1이 된다. 값이 다른 곳에 대입되도 프로퍼티의 weak 속성 때문에 참조 카운트가 늘어나지 않기 때문이다. 반면, A2는 strong 프로퍼티 속성 때문에 참조 카운트가 2가 된다.
메소가 끝날 때는 각각 객체의 참조 카운트를 1씩 줄이므로, A1, A2의 참조 카운트는 각각 0과 1이 된다. A2는 아직 참조 카운트가 남아있어 메모리 해제따위 하며 반응을 안보이겠지만, A1은 참조 카운트가 0이 됐기 때문에 가지고 있는 프로퍼티에 대해서도 메모리 해제를 해야한다. 즉, A1의 value2의 참조 카운트를 1 감소시키며, 이는 A2의 참조 카운트가 결국은 0이 됨을 의미한다. A2는 결국 메모리에서 제거되어 메모리 누출 현상은 막았다.
이걸 이해하는데 한나절이 걸렸다. strong, weak라는 것은 Objective-C에만 존재하는 뭔가 대단한 개념인 줄 알았다. 그러나 필요한 속성이었다. 그리고 ARC가 있어 더욱 쉽게 쓸 수 있다는 것도 알 수 있을 것 같다. 델리게이트와 데이터 소스에 대해서는 오브젝티브C의 표준 규칙이라고 하면서 유지 사이클을 막기 위해 weak (약한 참조)를 쓰라고 하는데 아직은 크게 와닿지 않지만, 머지 않아 곧 이해할 수 있을 것 같다.
'Software > iOS & Objective-C' 카테고리의 다른 글
iOS Crash Log 추출 (0) | 2015.11.06 |
---|---|
Xcode 개발관련 설정 (0) | 2015.09.10 |
[iOS] AES256 Descryption 에 대하여... (0) | 2014.02.11 |
NSString 변환 <—> NSData, char[] (0) | 2014.02.11 |
iPhone Application Life cylce (0) | 2013.05.16 |