본문 바로가기
old/iPhone 개발 이야기

iPhone 개발시 메모리 관리 지침!!

by 열야 2009. 11. 29.
C언어 계열 프로그래밍을 할 때 가장 중요한 것 중에 하나가 바로 메모리 관리입니다.
C언어는 당연히 그렇고 C++ 역시 다르지 않습니다. C언어로 부터 파생된 Object-C의 경우 두말할 필요 없겠지요?

C언어의 경우 매우 명확한 메모리 관리 기법을 제공합니다.
메모리 생성은 alloc계열 함수(alloc, malloc, calloc)로 하고, 자신이 alloc한 함수는 어떤 경우든, free를 호출해서 해제해야만 메모리 릭이 발생하지 않습니다.

그렇다면 iPhone에서의 Object-C는 어떨까요?
다음의 말을 기억해야 합니다.
"자신이 생성한 메모리는 자신이 release해야만 한다", 그러나, "자신이 생성하지 않은 메모리는 release해서는 안된다"
매우 명확해 보이기는 하지만, 실제로 프로그램하다보면 쉽지 않다는 것을 알 수 있습니다.
그 이유는 C언어에는 없는 retain개념과 autorelease개념이 있기 때문입니다.

1. 자신이 할당한 메모리란 무엇인가?
간단하게 자신이 할당한 메모리는 다음의 이름이 들어간 함수를 호출한 경우입니다.
new
alloc
retain
copy

2. 자신이 할당하지 않은 메모리란 무엇인가?
1번에서 말한 함수 이외에 CLASS에서 제공하는 constructor에 의해서 제공되는 메모리입니다. 예를 들면 다음과 같은 것들이지요
UIImage *newImage = [UIImage imagenamed:@"abcd.png"];

이 경우에는 자동으로 autolease pool에 들어가게 되어서 언제든 자동으로 해제되게 됩니다.

그러나, 1번에서 말한 것 같이 retain함수에 의해서 메모리에 대한 소유권을 취득한 경우, 자동으로 해제되지 않습니다. 코드로 보자면 이렇지요
UIImage *newImage = [[UIImage imagenamed:@"abcd.png"] retain];

그리고 한가지 더!!
1번에서와 같은 함수를 써서 메모리를 할당하더라도 강제로 autorelease함수를 부른 경우, 자동으로 메모리가 해제되므로 release함수를 부르는 경우, 프로그램이 마구 죽습니다!!
UIView *newView = [[[UIView alloc] initWithFrame:CGRectMake( x, y, width, height)] autorelease];
마구 죽는 이유는 위와 같이 함수를 호출 한 후,
[newView release];
를 호출하면, 바로 죽지 않고, 시스템에서 해당 메모리를 자동으로 해제하기 위해서 autorelease pool에서 해당 메모리 값에 대해서 rereleae를 호출합니다. 그 순간 프로그램은 DIE~~~~!!
이렇게 강조하는 이유는, 디버거로는 죽는 이유를 잡는 것이 거의 불가능에 가깝기 때문입니다. 이런 문제가 발생하기 시작하면, 프로젝트 기간이 길어지거나, 심한 경우 처음부터 다시해야 하는 상황이 발생하기 때문입니다.

3. retain이란 무엇인가?
C언어에서는 메모리에 대한 소유권에 대한 개념이 약합니다. 메모리에 대해서 한 곳에서 alloc을 한 경우, 그냥 free하면 됩니다만, 이런 경우, 똑같은 내용을 사용하고자 하는 경우 동일한 형태의 변수에 대해서 alloc하거나 free를 조건부로 해야 하는 경우가 생깁니다.
이러한 불편함을 없에고, 좀더 캡술화된 구현을 위해서 retain count라는 것을 사용합니다.
즉, memory공간에 대해서 해당 메모리 영역을 사용하는 코드의 개수를 써 놓는 것입니다. 예를 들어서,
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake( x, y, width, height)];
이 경우 newView에 대한 retain count가 1입니다. 그런데, 다른 영역에서 이를 사용하기 위해서 retain을 한 경우
UIView *duplicatedView = [newView retain];
newView의 retain count 는 2가 됩니다. 다시 여기에서
[newView release];
를 해도 retain count가 1이 되므로 실제적으로는 메모리가 해제되지 않습니다.

이러한 기법은 너무나 많이 사용됩니다. 예를 들면 다음과 같습니다.
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake( x, y, width, height)];
[currentViewController setView:newView];
[newView release];
위 코드는 일반적으로 view를 생성해서 viewController에 view로서 대입하는 코드 입니다. 앞에서 설명한 것과 같이 newView에 메모리를 할당한 후, 마지막에 release를 했습니다. 그럼에도 불구하고 이 코드가 정상적으로 작동하는 이유는 setView를 하는 함수내에서 retain을 하기 때문입니다.

4. autorelease의 사용은 조심해서 해야 한다.
autorelease는 할당한 메모리를 자동으로 해제하는 코드입니다. 기본적으로 autorelease는 한번실행되는 시레드 상에서는 해제되지 않는다고 생각하시면 됩니다. 그러나, 코드가 끝나고 제어를 시스템에 넘긴 상태에서는 언제든 해제될 수 있으니 왠만하면 alloc, release루틴을 사용하는 것이 좋습니다.
그럼에도 불구하고 너무나 편리한 함수이므로 다음과 같은 상황에서 사용하면 좋을 것입니다.
 - 특정 함수내에서 메모리를 할당해서 리턴해야 하는 경우
 - 메모리가 재 사용될 가능성이 있는 코드
(더 많은 사용처가 있을 것 같지만, 제 경우 위의 두 가지에서만 사용합니다.)

4.1 특정 함수내에서 메모리를 할당해서 리턴하는 경우
예를 들어서, 특정 함수를 부르면 자동으로 view를 생성해서 모든 기본 설정을 완료하면 좋겠다라고 생각하는 경우 추가적은 subclass를 만드는 것보다 간단하게 함수로 만드는 것이 편리할 것입니다.
- (UIVIew *) createBasicViewWithFrame:(CGRect)rect
{
UIView *newView = [[[UIView alloc] initwithFrame:rect] autorelease];
/* 그외 여러가지 설정 들...*/

return newView;
}

이렇게 함수를 작성한 경우, 리턴된 값에 대해서 release함수를 따로 부르지 않아도 된다는 장점이 있습니다. 만약 이를 C언어 코드로 바꾼다면 함수 밖에서 메모리를 할당하고 함수를 부를때, call by reference로 전달한후, setting이 완료되면 사용 후, free를 해줘야 합니다만, 위와 같이 하면 함수안에서 모든 것이 해결되니 편리합니다.

4.2 메모리가 재 사용될 가능성이 있는 코드
이는, 사용자 코드가 아닌 시스템 코드에서 제공하는 경우가 있습니다 대표적인 예가 UITableViewCell이 경우입니다.
MEListCellThumb *cell = (MEListCellThumb *)[tableView dequeueReusableCellWithIdentifier:thumbCellIdentifier];
if (cell == nil)
{
            [[NSBundle mainBundle] loadNibNamed:@"MEListCellThumb" owner:self options:nil];
}

5. 그외 주의할 점.
iPhoneOS 3.0이 된 이후에 setValue함수 설정에 retain을 하는 경우가 많이 있습니다. 그러므로 accessor함수를 호출할 때는 언제나 자동으로 retain이 되는 것으로 생각해야 합니다. 그러므로 자신이 짜는 코드내에서 변수를 직접 억세스 할 것인지 accessor함수를 쓸 것인지 통일해서 사용하는 것이 실수를 줄이는 방법입니다.


자세한 사항은 다음 문서를 참고하세요
iPhone프로그램을 원활히 하고자 한다면 다음의 문서를 3번 정독하기를 추천합니다.
http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html