본문 바로가기

iOS개발팁

아리송한 ObjC의 레퍼런스 카운트

오늘 회사에서 같이 아이폰개발을 하시는 파란망토차차(@jihyun2ya)님과 Objective-C 언어의 메모리관리 부분에 대한 의아한 점을 발견했다. 문제의 코드는 아래와 같다.




UIButton *button = [[UIButton alloc] init];
NSLog(@"button's reference count is %d", [button retainCount]);
[button release];
NSLog(@"button's reference count is %d", [button retainCount]);
코드를 보면 button객체의 레퍼런스카운트를 출력하는 내용이다. 출력 결과는 어떻게 될까?
2010-07-15 15:19:42.408 button's reference count is 1
2010-07-15 15:19:42.409 button's reference count is 1
둘 다 1이 출력된다. 0을 기대했지만 결과는 이상하게 1이 출력되었다. 사실 코드를 살펴보면 의미상으로 맞지 않다는 것을 알 수 있다. 레퍼런스카운트가 1인 button객체를 릴리즈하여 레퍼런스카운트가 0이 되어버린  button객체에게 다시 retainCount라는 메세지를 보내고 있으니 의미가 맞지 않는 것이다. 하지만 이상하게도 0으로 출력되어야 할 레퍼런스카운트가 1이 출력되었다. 뭔가 확실히 해 두지 않으면 버그를 생산하는 원인이 될 수 있겠다 싶어 약간 실험을 해 보았다.
UIButton *button = [[UIButton alloc] init];
NSLog(@"button's reference count is %d", [button retainCount]);
[button release];
NSLog(@"button's reference count is %d", [button retainCount]);
[button retain];
NSLog(@"button's reference count is %d", [button retainCount]);
위의 코드를 보면 제일 마지막에 출력하는 button의 레퍼런스카운트가 1이 되어야 맞을 것 같지만 아쉽게도 결과는 아래와 같다.
2010-07-15 15:27:12.031 button's reference count is 1
2010-07-15 15:27:12.033 button's reference count is 1
2010-07-15 15:27:12.033 button's reference count is 2
레퍼런스카운트가 2가 출력되는 것이다. Objective-C언어에서는 객체의 레퍼런스 카운트가 0이 될 때, dealloc을 호출한다. 그렇다면 위의 버튼 예제에서는 dealloc이 호출되었을까? dealloc이 호출되는 것을 확인하기 위해서 클래스를 만들어 보았다.
@interface MyObject : NSObject
{   
}
@end
@implementation MyObject
- (id) init
{
    if(self = [super init])
    {
        NSLog(@"[init] reference count: %d", [self retainCount]);
    }
    return self;
}
- (void) dealloc
{
    [super dealloc];
    NSLog(@"[dealloc] reference count: %d", [self retainCount]);
}
@end
객체를 초기자와 소멸자에서 객체의 참조횟수를 출력하는 것이 전부이다. 그리고 아래와 같이 테스트 코드를 작성하였다.
MyObject *obj = [[MyObject alloc] init];
NSLog(@"obj's reference count is %d", [obj retainCount]);
[obj release];
NSLog(@"obj's reference count is %d", [obj retainCount]);
[obj retain];
NSLog(@"obj's reference count is %d", [obj retainCount]);
결과는 아래와 같다.
2010-07-15 16:25:35.598 [init] reference count: 1
2010-07-15 16:25:35.599 obj's reference count is 1
2010-07-15 16:25:35.600 [dealloc] reference count: 1
2010-07-15 16:25:35.600 obj's reference count is 1
2010-07-15 16:25:35.601 obj's reference count is 2
객체의 소멸자인 dealloc은 참조횟수가 0일 때 호출된다. 하지만 위의 로그를 보면 참조횟수가 1일 때 호출되고 있음을 알 수 있다. 유추해 보건데 객체의 dealloc이 호출되고 나서 객체의 참조횟수가 0이 되는 것 같다. 그렇다고 해도 위의 로그는 도저히 이해할 수가 없었다. dealloc까지 된 객체의 참조횟수가 어떻게 retain 되어 2가 될 수 있단 말인가...

혹시 참조횟수를 줄이는데 시간차가 있는 것은 아닐까 생각되어 여러번 반복하여 앱을 실행해 보았다. 그랬더니 허무하게도 어떨 때는 04번 라인에서 메모리크래쉬가 일어나 실행되지 않았고 어떨 때는 06번 라인까지 무사히(?) 실행되어 참조횟수가 2가 되고 앱이 잘 실행되었다. 04번라인에서 메모리크래쉬가 일어나는 것이 정상적인 동작이다.
01: MyObject *obj = [[MyObject alloc] init];
02: NSLog(@"obj's reference count is %d", [obj retainCount]);
03: [obj release];
04: NSLog(@"obj's reference count is %d", [obj retainCount]);
05: [obj retain];
06: NSLog(@"obj's reference count is %d", [obj retainCount]);
이건 참 뭥미? 라고 할만한 것이었다. 참조횟수가 0이 될 때의 시간차가 Objective-C의 언어적인 특징인지 아니면 언어의 깊은 곳을 잘 알지 못해서 그런 것인지 모르지만 버그를 만들어 낼 수 있는 코드이기에 해결 방법을 생각해 보았다. 답은 아주 간단했다. 바로 해제하고 nil로 설정하여 명시적으로 메모리블럭의 인스턴스와 참조자간의 연결을 끊는 것이다.
01: MyObject *obj = [[MyObject alloc] init];
02: NSLog(@"obj's reference count is %d", [obj retainCount]);
03: [obj release]; obj = nil;
04: NSLog(@"obj's reference count is %d", [obj retainCount]);
05: [obj retain];
06: NSLog(@"obj's reference count is %d", [obj retainCount]);
07: [obj release];
이렇게 하면 nil 에게 메세지를 보내는 것이기에 아무런 문제가 없게 된다. nil을 release 해도 문제가 없다. nil에게 메세지를 보낼 수 있는 것은 Objective-C언어의 특징이다. 실행 결과는 아래와 같다.
2010-07-15 16:41:40.757 [init] reference count: 1
2010-07-15 16:41:40.761 obj's reference count is 1
2010-07-15 16:41:40.764 [dealloc] reference count: 1
2010-07-15 16:41:40.765 obj's reference count is 0
2010-07-15 16:41:40.765 obj's reference count is 0
참조횟수도 0이고 아무런 문제 없이 앱이 잘 실행된다. 따라서 아래와 같은 매크로를 정해서 쓰는 것도 나쁘지 않을 것 같다.
#define SAFE_RELEASE(a) if(a!=nil){ [a release]; a=nil;}
MyObject *obj = [[MyObject alloc] init];
NSLog(@"obj's reference count is %d", [obj retainCount]);
SAFE_RELEASE(obj);
NSLog(@"obj's reference count is %d", [obj retainCount]);
[obj retain];
NSLog(@"obj's reference count is %d", [obj retainCount]);
SAFE_RELEASE(obj);
덧) 제가 생각하는 것에 오류가 있다면 댓글로 지적 부탁드립니다. ;)
덧) 제목이 너무 자극적이어서 수정했습니다 ;)
 

'iOS개발팁' 카테고리의 다른 글

아이폰 OpenGL ES 튜토리얼 시작  (0) 2010.07.19
ObjC 객체의 retainCount 이야기  (0) 2010.07.16
트윗 WWDC10  (0) 2010.06.19
트윗 WWDC10  (0) 2010.06.19
SIO2와 Blender를 만나다.  (2) 2010.06.14