Objective C 2.0에는 자바나 C#과 같은 Garbage Collection이 도입되었지만, iOS에서는 이 기능을 지원하지 않고
메모리 관리를 직접 해주어야 합니다.
C에서 포인터하면 어렵고 자바 등 GC가 지원되는 언어만 사용해서 메모리 관리가 어렵게 느껴질 수도 있지만,
C나 C++에서는 malloc과 free 또는 new와 delete만 있을 뿐 메모리 관리는 직접 해야 하지만,
Objective C에서는 CoreFoundation의 retain/release/autorelease 를 통해 별다른 고민없이 메모리관리를 할 수 있습니다. 원리만 잘 이해하고 원칙만 잘 지킨다면 memory leak이 나거나 미리 free가 된 곳에 접근을 해서 bad memory access 가 나는 경우는 태초부터 없앨 수 있습니다~
먼저 retain과 release에 대해서 설명하자면 아주 간단합니다.
retain은 해당 오브젝트의 reference값을 1 증가 시킵니다.
release는 해당 오브젝트의 reference값을 1 감소시키는데 만약 0이 되면 바로 dealloc을 호출하여 해당오브젝트는 메모리 해제가 됩니다.
Objective C에서 object의 라이프라이클은 처음에 alloc이 되면서 메모리할당이 되고 reference카운트 1로 시작하며 retain이 불려 값이 올라가거나 바로 release가 불려 0이 되면서 해제됩니다.
여기서 메모리 관리를 할때 retain을 한 곳에서 release를 한다면 메모리관리는 아주 단순해집니다. 현재 메소드내의 로직에서 object를 retain했다면 나가기전에 release만 해주면 결국 memory leak은 나지 않을테고 아무리 복잡해도 아주 단순하게 됩니다.
그런데 문제가 생깁니다.
- (NSString *)stringWithInteger:(NSInteger)num {
// 이 메소드에서 integer값을 받아 NSString으로 돌려받고 싶다고 합시다.
NSString *ret = [[NSString alloc] initWithNSInteger:num]; // object를 retain합니다.
[ret release]; // object를 release합니다.
return ret;
}
메소드내에서 반드시 retain하면 release하도록 만들고 보니 return 될때 이미 reference count가 0이 되어 메모리에서 해제되고 죽은 오브젝트가 떨어집니다. 만약 이 오브젝트에 접근을 하면 bad memory access가 100% 발생하게 되지요.
값을 리턴해야 하므로 울며 겨자먹기로
- (NSString *)stringWithInteger:(NSInteger)num {
// 이 메소드에서 integer값을 받아 NSString으로 돌려받고 싶다고 합시다.
NSString *ret = [[NSString alloc] initWithNSInteger:num]; // object를 retain합니다.
// [ret release]; // object를 release합니다.
return ret;
}
받은 곳에서 release하기로 합니다.
- (void)print {
NSString *retString = [self stringWithInteger:10];
NSLog(@"the number is %@", retString);
[retString release];
}
별로 쿨하지 못합니다. stringWithInteger가 주는 NSString은 받은 곳에서 반드시 release한다고 표기라도 해 둬야겠네요. 아예 object를 return할 경우 release 의무는 전부 caller에게 넘기는 것도 방법입니다.
하지만 그렇게 되면 바로 위의 예처럼 먼저 object의 reference를 먼저 할당하고 사용한 다음 나중에 반드시 release를 호출해줘야하는 번거로움이 생기죠.
이런 메소드 본적 있지 않나요? [NSArray arrayWithObject:object];
object를 넘겨주면 그 object를 담은 NSArray를 떨굽니다. 그런데 위처럼 release를 할 필요가 없지요.
바로 autorelease가 사용되기 때문입니다.
autorelease는 해당 오브젝트를 autoreleasepool에 넣어두기만 합니다. 그리고 나중에 autoreleasepool의 drain이 호출될때 release가 불려집니다.
autorelease를 사용해서 코드를 바꿔보면
- (NSString *)stringWithInteger:(NSInteger)num {
// 이 메소드에서 integer값을 받아 NSString으로 돌려받고 싶다고 합시다.
NSString *ret = [[NSString alloc] initWithNSInteger:num]; // object를 retain합니다.
[ret autorelease];
return ret;
}
- (void)print {
NSLog(@"the number is %@",
[self stringWithInteger:10]);
}
훨씬 깔끔해졌네요. stringWithInteger는 이제 object를 retain하고 release모두 하게 됩니다.
print는 object release를 할 필요가 없어졌습니다.
자 그럼 다시 언제 release를 해야하는지 살펴 봅시다. 아주 간단합니다.
alloc또는 copy를 했다면 release를 해줍니다. 그렇지 않다면 그냥 사용합니다.
- (void)a {
NSString *a = [[NSString alloc] initWithFormat:@"hello world %d", 3];
//이렇게 alloc을 불러줬다면 이 메소드가 빠져 나가기 전에 release를 해 줍니다.
[a release];
}
만약 이 a를 리턴해야 한다면 [a autorelease];를 불러주면 되는 겁니다. copy의 경우도 마찬가지입니다.
근데 이 경우는 어떤가요
- (void)b {
NSString *b = [NSString stringWithFormat:@"hello world %d", 3];
}
이때는 release를 해 줄필요가 없습니다. NSString의 stringWithFormat이라는 class method내부에서 autorelease를 해서 던져주기 때문입니다.
그리고 b에서는 alloc이나 copy를 호출하지 않았으므로 release할 의무도 없습니다.
아주 단순합니다만 위와 같은 원칙만 지켜서 코딩을 해주면 memory leak이나 bad memory access 절대 안난다고 제가 보장합니다.!!!!!
메모리관리원칙 : alloc/copy 한 메소드내에서 반드시 release/autorelease를 호출한다.
이 한가지만 지키면 됩니다.
그럼 여담으로 autorelease는 어떻게 작동하는지 살짝 설명해 볼까 합니다.
autorelease는 위에서도 설명했듯이 autoreleasepool에 푸시만 하고 release는 뒤로 미룹니다. 이렇게 autoreleasepool에 푸시된 object가 release되는 것은 autoreleasepool의 drain이 호출될 때 입니다.
(정확히는 NSAutoreleasePool 입니다. 링크 달아요 : http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
)
과연 autoreleasepool의 drain은 언제 호출이 될까요?
예... autoreleasepool의 drain을 직접 호출할때 입니다. 만약 콘솔 프로그램을 한다면 autoreleasepool을 정의하고 drain을 호출하는 것은 직접 다 해줘야 합니다.
그런데 iOS 또는 Mac용 어플리케이션 프로그램을 할때는 조금 다릅니다.
int main()을 타는 콘솔어플리케이션이 아닌 NSApplication또는 UIApplication에서 시작하는 iOS/Mac 어플리케이션은 진입후 메인 쓰레드 ( 또는 UI쓰레드)는 event루프를 계속 돌게 됩니다.
event루프에서 touch이벤트나 network, disk i/o, timer 같은 이벤트가 오면 그 이벤트를 받아서 처리합니다. 그리고 이벤트 처리가 끝나면 다시 다른 이벤트를 기다리며 standby상태가 됩니다.
(추가 설명하자면 그러므로 메인쓰레드에서는 절대 시간이 많이 걸리는 일을 해서는 안됩니다. UI가 블럭되는 일이 발생하거든요... 그런 작업이 필요하다면 반드시 background thread로...)
이 autoreleasepool의 drain은 바로 이 이벤트처리가 끝나고 다시 다른 event를 기다릴때 호출 됩니다. 즉 하나의 이벤트처리가 끝날때마다 drain이 호출되어 autorelease를 찍어둔 것들이 죄다 release가 호출된다고 보면 됩니다.
그러므로 stringWithFormat을 호출해서 받은 값은 이벤트로직을 완전히 빠져나가기 전까지는 계속 유효하지만
NSString *data = [NSString stringWithFormat:@"hello %d", 1]; 와 같이 data에 레퍼런스를 가지고 있다가
다음번 클릭때 사용하려고 한다면 보나마나 bad memory access가 납니다. 이미 다음번 클릭 이벤트가 오기전에 autoreleasepool의 drain이 호출되고 release가 불려서 dealloc이 되었기 때문이죠.
만약 현재의 이벤트로직을 빠져나가도 값을 유지하고 싶다면, NSString *data를 class에 instance variable로 선언하고
data = [[NSString stringWithFormat:@"hello %d", 1] retain]; 또는 엣지있게
data = [[NSString alloc] initWithFormat:@"hello %d", 1]; 으로 씁니다.
그리고 - (void)dealloc {
[data release];
[super dealloc];
}
하면 되겠죠...
아참.... 위에서 메소드내에서 alloc/copy 한 것은 메소드가 끝나기 전에 반드시 release/autorelease한다고 했지만, 그것은 scope가 메소드내인 경우 그런 것이고 class의 instance variable이라면 위처럼 class가 dealloc될때 해 주는 것이 맞습니다. 즉 class scope가 되는 것이죠~
아마 이제 더 이상 memory leak, bad memory access 구경할 일은 없겠죠?
혹시 궁금한게 있으면 댓글 써 주세요.
autoreleasePool을 이용하면 pool에 가지고 있다가 drain될때 전부 release한다고 이해했습니다.
보통 [pool drain]은 어플리케이션이 끝나는 지점에 선언되있더군요.(제가 아는한도에서;;)
뒤집어서 생각하면, 앱이 끝나기전에는 계속 메모리를 점유하고 있다는것 같은데요..
만약 그렇다면..앱에 따라서는 메모리 오버플로우 현상같은게 생기지는 않나요?
말씀드렸듯이 iOS/MAC 어플리케이션은 메인 이벤트 루프를 돌면서 계속 drain을 호출해줍니다.
그러나 콘솔 어플리케이션은 다르지요. 직접 drain을 호출해야하는데, 결국 내부적으로 메세지 루프가 있다면 메세지 루프를 돌면서 할 가능성이 높지요.
책에 나온 예제 애플리케이션은 그냥 설명차원에서 쓰는거고 모든 것이 다 끝난 마지막에 drain을 호출 하는 것일 뿐이구요^^ 어차피 종료직전이니 해도 그만 안해도그만;
iphone을 예로 들면
UIApplication이 실행되고
for(;;) {
waitAndProcessEvent();
[autoreleasepool drain];
}
뭐 이런 느낌이지요~
그럼 해당앱을 반복적으로 끄고 켜고..하다보면 기기가 메모리 부족으로 재부팅되거나 하게될까요?
아이폰SDK 예제를 보면서 따라하는데 NSAutoreleasePool을 이용한 예제가 없어서;;
autorelease만 사용하지요...
그리고 앱이 종료되면 어차피 메모리는 모두 반환됩니다.~~~ 돌아가는 동안 leak이 생기느냐 하는문제일뿐이지요 만약 앱내부에서 명시적으로 free를 안한다고 반환안되면, 앱이 강제종료되는 경우메모리가 계속 줄어들겠지요;;
그래서 제가 경험한 것들 아는 것들, 그리고 책을 봐선 알기 힘든 것들에 대해서 앞으로 쭈욱 써보려 합니다.
그래도 애플 문서들은 참 잘 되어 있습니다. 애플 문서들이 비록 영어로 도배되어 있긴 하지만 틈틈히 읽어보세요...
안드로이드는 정말 문서가 엉망인데 ㅋㅋ 애플은 문서만 보고도 왠만해선 인터넷뒤지고 할일이 없어요
수고하셨습니다 !