Objective-C의 메모리관리
가끔 인터넷을 통해 iOS 관련 소스들을 참고하면 메모리 관리가 엉망인 경우가 자주 보인다.
개인적으로 답답해서 Objective-C의 메모리 관리가 어떻게 되는지, 어떤식으로 프로그래밍 해야 제대로 메모리 관리를 한 것인지 간략하게 적어보려고 한다. 본인도 틀린부분이 있을 수 있으니 피드백은 환영입니다.
가장먼저 Objective-C(이하 줄여서 ojbc로 하겠다)의 메모리 관리의 가장 중요한 개념인 retain count의 개념이다.
objc의 객체는 allocation(alloc method를 호출할때) 될때 retain count가 1이 되고 이 숫자가 0이 될 때 객체가 메모리에서 해제된다. 이 숫자는 10이 될 수도 100이 될 수도 있지만 0이 되야만 객체가 메모리에서 해제가 된다.
반대로 release method를 호출하면 retain count는 하나가 줄게된다.
예를 들면
NSObject* obj = [NSObject alloc]; -> retain count 1 상승
[obj release]; -> retain count 0 -> 메모리에서 해제
이렇게 된다.
우리는 이 retain count 개념을 통해 간단하게 메모리 관리를 할 수 있다.
두번째로 autorelease이다
흔히 objc에서 객체를 생성할때 두가지 방법을 사용한다.
첫번째로
NSString* str = [[NSString alloc] initWithString:@”string”];
이런식으로 allocation 후 initialize 해주는 방법
두번째로
NSString* str = [NSString stringWithString:@”string”];
이런식으로 간단하게 객체를 생성하는 방법(From C++ to Objective-C 에서는 Convinience constructor라고 부르더라)이 있다.
아마 stringWithString: method는 이렇게 구현되어있을것이다.
+(id)stringWithString:(NSString*)string{
id obj = [[self class] initWithString:string]; // [self class]는 자기 자신의 클래스를 자동으로 return해준다.
return [obj autorelease];
}
마지막 return문을 보면 autorelease라는 method를 호출한 것을 볼 수 있다. autorelease는 retain count를 1 감소시키지만 실제로는 감소되지 않은것으로 취급한다. 그렇다면 도대체 언제 감소되는 것인가? 정답은 NSAutoreleasePool에 있다. autorelease method를 호출하면 그 객체는 현재 생성되어있는 pool에 등록되게 되고 그 pool이 release 되는 순간 “정말로” retain count가 감소되게 된다. 그래서 모든 objc 어플리케이션에는(정확히는 모든 Thread마다 하나씩) NSAutoreleasePool이 있어야한다. 여러분의 iOS 프로젝트의 main.m에도 NSAutoreleasePool이 있을 것이다.
그렇다면 이 NSAutoreleasePool은 어떻게 활용되야 하는가? 다시 위의 예제를 보면 객체를 생성하고 pool에 등록한 후 return하는것을 볼 수 있다. 이렇게 함으로써 자신이 생성한 객체의 메모리 누수를 막을 수 있게된다. 그래서 objc에서 자신이 allocation한 객체를 return할 때에는 반드시 pool에 등록을 해야한다. 이것은 하나의 약속이다.(initializer가 init으로 시작하는것과 마찬가지이다)
그렇다면 이 객체를 받은 프로그래머는 그냥 이놈을 써도 좋은가? 그것은 아니다. pool에 등록된 객체는 언제 해제될지 모르는 시한폭탄과도 같은 존재이다. pool은 Thread마다 있는 존재이므로 multi Thread인 경우에는 더더욱 위험하다. 그래서 Convinience constructor를 통해 생성한 객체를 생성한 method가 아닌 다른 method에서 사용할 경우에는 반드시 retain method를 호출 해주어야한다. 간단하게 예를 들어보자
다음과 같은 클래스가 있다.
@interface ExClass : NSObject{
NSString* str;
}
@end
@implementation ExClass
- (void)mkStr{
NSStirng* tmp = [NSString stringWithString:@”asd”]; // tmp 객체는 이 method내에서만 사용되므로 다른 pool과 연관이 될 염려가 없다. 그러므로 retain을 하지 않아도 된다.
str = [[NSString stringWithFormat:@”%@%@”, tmp,@”123”] retain]; // str 객체는 클래스 “ExClass”의 멤버이기 때문에 다른 pool과 연관될 가능성이 있다. 그러므로 retain을 해서 안전하게 보관해야 한다.
}
@end
다른 pool과 연관되는것이 상상이 되지 않는 분들을 위해 간단한 예를 들어 보겠다.
str 객체에 웹에 있는 게시글의 제목을 저장한다고 하자 네트워크 연동이므로 당연히 Multi Thread를 사용할 것이다 네트워크 관련 Thread를 B라고 하면 B는 자신의 pool을 가지고 있을것이고 B의 thread 에서 Convinience constructor를 통해 생성된 객체 str은 B의 pool에 등록이 될것이다. 네트워크 작업이 끝나고 B의 pool도 release된다면 str의 retain count는 감소할 것이며 retain을 호출 하지 않았다면 메모리에서 해제될 것이다. 그리고 UI관련 Thread인 A에서 str을 화면에 띄우려고 하면 str은 이미 B의 pool이 release되면서 메모리에서 해제가 되어있을것이다. 이러한 상황을 방지하기 위해서는 반드시 retain을 호출 해주어야 한다.
다음은 property이다. 이곳에서는 property의 최소한만 소개할 것이다.
자주 값이 변경되는 멤버는 변경되기전에 자신이 retain했던 객체를 release해주어야 할것이다.
예를 들어보자(mInstance는 클래스의 멤버이고 instance는 멤버에 대입될 객체이다.)
if (mInstance != nil){ // mInstance에 값이 이미 있으면
[mInstance release]; // release하고
}
mInstance = [instance retain]; // retain해서 새로운 값을 안전하게 보관한다.
값을 대입할때마다 일일히 저런식으로 하는것은 매우 번거롭다. 그래서 setter를 구현해서 관리를 하게된다. setter의 예를 들어보겠다.
- (void)setMInstance:(id)instance{
if (mInstance != nil){
[mInstance release];
}
mInstance = [instance retain];
}
이런식으로 구현해 놓으면 필요할때마다 setter만 호출해서 대입하면 되기 때문에 메모리도 편하고 안전하게 관리할 수 있다. 그런데! 이것마저도 멤버의 수가 많아지면 소스의 길이가 길어져 복잡해지기 마련이다. 그래서 objc2.0에서 새로 나온것이(다른 많은 언어에는 이미 있었지만) property이다. 일단 책이나 예제에서 많이 본 property의 예를 보자
@property (nonatomic, retain) id instance;
그리고 .m 파일에서
@implementation …
@synthesize instance;
먼저 property는 instance라는 getter와 setInstance:라는 setter를 생성해준다.(원한다면 getter, setter의 이름을 바꿀수도, property와 실제 멤버의 이름을 다르게 할수도 있지만 지금은 다루지 않겠다.)
그리고 (nonatomic, retain)은 property의 getter, setter 옵션이다. nonatomic(Non-Atomic)은 멀티 Thread에서의 안전을 보장하지 않는다는 뜻이다. 그리고 retain은 setter를 통해 값이 바뀔때마다 전의 값을 release하고 새로운 값을retain을 해주겠다는 뜻이다. 위의 setMInstance: 와 똑같이
이런식으로 getter, setter를 구현하거나 property를 만들면 그 객체의 메모리 관리는 크게 신경 안써도 되지만 꼭 release를 해야하는 곳이 있다. 바로 dealloc 이다. dealloc에서는 반드시 수동으로 모든 멤버를 release해주어야 한다. 또 iOS의 경우에는 ViewController에서 viewDidUnload에서 멤버를 해제하거나 프로퍼티에 nil을 대입합으로써 view와 관련된 멤버를 해제해야한다.
이상 본인이 알고있는 objc의 메모리 관리에 대해서 적어보았다. 위의 내용을 잘 숙지하고 Xcode의 “Build and Analyze”기능을 잘 사용한다면 메모리 leak없는 objc 프로그래밍을 할 수 있을것이다.
피드백은 환영!