[iOS] Practical Memory Management

iOS  프로그램 개발에 있어서 메모리 관리를 쉽게 하고, 안정성을 높이는 방법을 알아보자.

iOS 메모리 관리는 객체의 소유권을 바탕으로 한다. 객체의 소유권 정책(ownership policy)은 참조 카운트(reference counting)에 의해 이루어 지는데 다음과 같은 방식으로 계산된다.

  • 객체를 생성하면 retain count는 1이다.
  • retain 메시지를 전송하면 retain count는 1 증가한다. (소유권을 갖는다)
  • release 메시지를 전송하면 retain count는 1 감소한다.(소유권을 반환한다)
  • autorelease 메시지를 전송하면 autorelease pool block 종료시점에 retain count는 1 감소한다.
  • 객체의 retain count 가 0이 되면 객체는 해제(deallocated)된다.
  •  NSObject의 retainCount 메소드는 개발자가 의도한 숫자를 반환하지 않을 수 있으니 디버그 이외에는 사용하지 말자.

iOS Application을 위한 Apple의 권장 메모리 관리 방법은 아래와 같다.

1. 접근자 메소드(Accessor Method) 사용

클래스의 인스턴스 변수는 사용되는 동안 해제되어서는 안되다. 따라서 변수에 객체를 할당할 때 객체의 소유권을 요청해야 하며 사용 후에는 객체의 소유권을 반환해야 한다.

접근자(Accessor) 메소드는 이를 쉽게 함으로써 빈번하게 개발자가 객체 소유권을 획득,반환하는데서 발생할 수 있는 문제들을 줄인다.

property는 두 가지(getter/setter) 접근자 메소드를 제공한다. @synthesize 는 컴파일러가 자동으로 접근자 메소드를 생성하게 하지만, 자동으로 생성되는 접근자 메소드의 내부 구현 원리를 확인할 필요는 있다.

@interface Counter : NSObject
@property (nonatomic, retain) NSNumber * count;
@end

@implementation Counter

// getter
-(NSNumber *)count{
    return _count;
}

// setter
-(void) setCount : (NSNumber*)newCount{

    [newCount retain];
    [_count release];

    // make the new assignment
    _count = newCount;
}
@end

get 메소드에서는 retain, release 를 호출하지 않는다. 반면 set 메소드에서는 [newCount retain] 을 호출하여 언제든 해제될 수 있는 newCount 객체의 소유권을 확보한다.

[_count release]는 [newCount retain] 이후에 와야한다. 그 이유는 newCount 객체와 _count 가 동일할 경우,  _count 객체 release를 먼저 수행하면 _count 객체와 동일한 newCount 객체도 해제될 수 있기 때문이다. (참고로 objective-c 에서는 [_count release] 대신에 _count = nil  사용할 수 있다. 이는 _count 에 할당된 객체가 없을 때에도 코드가 동작하도록 한다.)

프로퍼티에 새로운 값이 할당되면 이전 값은 release 됨을 기억하자. 프로퍼티 값을 다른 변수에 retain 없이 할당하고 프로퍼티에 새로운 값을 설정하는 경우, 프로퍼티의 이전 값은 release 되므로 이전 값이 할당된 객체를 이용한 코드는 문제 발생 소지가 있다.(Automatic Reference Counting 의 경우에는 프로퍼티의 이전 값을 retain 없이 할당해도 객체 메모리가 해제되지 않음을 보장한다.)

2. 초기화 메소드 / dealloc 메소드에서는 접근자 메소드(Accessor Methods) 사용 금지

초기화 메소드(initializer method) 및 dealloc 메소드 내에선 접근자를 이용해서 변수를 할당해서는 안된다. 초기화 메소드에서 변수의 초기 값을 할당하려면 접근자가 아닌 인스턴스 변수를 이용해야 한다. 또한 dealloc 메소드에서 소유권 포기를 위한 release 도 아래 코드와 같이 인스턴스 변수를 이용해야 한다.

// 초기화 메소드
- init{

    self = [super init];
    if(self){
        _counter = [[NSNumber alloc] initWithInteger : 0];
    }
    return self;

}
// dealloc 메소드
-(void)dealloc{
    [_counter release];
    [super release];
}

3. Retain Cycle 발생하지 않도록 약한 참조(Weak Reference) 사용

retain은 객체 간 강한 참조(strong reference) 관계를 형성한다. 객체는 자신과 강한 참조 관계를 가진 객체가 하나도 없을 때까지 해제되지 않는다.

Retain Cycle 문제는 두 객체가 서로 강한 참조일 경우 발생한다. (두 객체 간 뿐만 아니라 다수의 객체가 강한참조로 원형으로 물려 있어도 발생한다.) 아래 그림을 살펴보자.

< From iOS Developer Library >

Document 객체는 여러 개의 Page 객체를 가지고 있고, 각각의 Page 객체는 자신이 어떤 Document 에 속해있는지 추적하기 위해 Document를 프로퍼티로 가지고 있다. 만약에 Document 객체가 Page 객체를 강한 참조로 소유하고, Page 객체가 Document 객체를 강한 참조로 소유하고 있다면 두 객체는 해제되지 않는다.(Retain Cycle 문제)

서로 강한 참조를 하고 있으므로 Document 객체의 참조 카운트는 Page 객체가 해제될 때까지 0이 되지 않고 Page 객체는 Document 객체가 해제될 때까지 참조 카운트가 0이 되지 않는다. 즉, 서로 객체가 해제될 때까지 대기하므로 두 객체는 해제되지 않는다.

Retain Cycle 문제 해결 방법은 약한 참조(weak reference)를 사용하는 것이다. 약한 참조는 객체의 소유권을 갖지 않는 객체 참조로 소스 객체의 참조 카운트가 증가하지 않는다. Apple은 부모 객체- Document 객체 -는 자식 객체- Page 객체 -를 강한 참조로 소유하고, 자식 객체는 부모 객체를 약한 참조로 소유하도록 권고하고 있다.

약한 참조는 소스 객체의 참조 카운트를 증가시키지 않으므로 언제든 객체가 해제될 수 있다. 따라서 약한 참조로 소유하고 있는 객체에 메시지를 전송할 때는 주의해야 한다. 만약 해제된 객체로 메시지를 전송한다면 프로그램은 문제를 일으킬 것이다.

약한 참조의 경우 참조된 소스 객체가 해제되면 참조한 객체에서 파악할 수 있도록 프로그램을 설계해야 한다. 즉, 약한 참조가 해제될 때 자신과 연결된 다른 객체에게 이를 알려줄 의무가 있도록 설계해야한다.

Notification Center에 객체를 등록하게 되면 Notification Center는 약한 참조로 객체를 저장하고 사전 정의된 컨디션이 되면 저장된 객체에 이를 알려준다. 만약 Notification Center 에 등록한 객체가 해제될 경우, 개발자는 Notification Center에 등록된 객체를 등록 해제 해야한다.

이와 마찬가지로 delegate에 할당된 객체가 해제되면 개발자는 delegate 링크를 해지시킬 필요가 있다. 링크 해지는 보통 dealloc 메소드에서 setDelegate : nil 을 통해 구현한다.

Cocoa Framework에서 약한 참조를 사용하는 경우는 Table data source,  Outline view item, Notification observer, Target 그리고 delegates 가 있다.

4. 사용하는 객체가 해제되지 않도록 주의

객체가 사용되는 동안에는 객체가 릴리즈되지 않음을 보장하도록 프로그램을 작성해야한다.

아래 case 1, case 2 와 같은 방법으로 다른 객체를 할당 받은 변수의 사용은 문제를 일으킬 수 있다. 이를 방지하기 위해 case 3처럼 retain을 통하여 객체를 소유하여야 한다.


// case 1
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid
// case 2
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child];
[parent release]; // or self.parent = nil;
// heisenObject could now be invalid

// case 3
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// use heisenObject
[heisenObject release];

콜렉션은 객체를 추가할 때 객체의 소유권을 갖고, 객체를 삭제할 때 소유권을 반환한다 .따라서 case 1의 경우, 콜렉션에서 삭제된 객체의 소유자가 유일하게 콜렉션 자신일 경우 hesienObject 사용에 있어 문제가 생길 수 있다.

case 2의 경우 [parent release] 라인이 parent 객체의 참조 카운트를 0으로 만들고, child 객체의 소유자가 유일하게 parent 이며, parent 가 해제될 때 child 가 release (not autorelease)된다면 hesienObject 사용에 문제가 생긴다.

5. dealloc 메소드에서 한정된 자원(Scarce Resource) 관리 금지

File, Network connections, Buffer 그리고 Cache 와 같은 자원을 dealloc 메소드 내에서 관리하면 안된다. 즉, dealloc 메소드 호출 시점을 개발자가 예상할 수 있다는 가정 하에 프로그램을 설계하면 안된다. dealloc 메소드 호출은 버그 등의 문제로 지연되거나 건너뛸 수 있다.

위에 언급한 종류와 같이 한정된 자원을 다룰 때는 자원이 더 이상 사용되지 않는 시점에, 정확히 자원이 해지되도록 디자인 해야 한다. 그렇지 않고 자원의 해지를 dealloc 메소드에서 한다면 다음과 같은 문제가 발생한다.

  • 객체 그래프(object graph)가 해체되는 메카니즘은 본질적으로 불규칙(non-ordered)적이다. 비록 개발자가 특정 순서를 예상할 수 있다 하더라도 항상 그렇지 않다. 만약에 객체가 autoreleased 되었다고 한다면, 해체 순서는 변경될 것이고 이는 예상치 못한 결과를 야기할 수 있다.
  • 위에 언급한 자원들이 원하는 시점에 릴리즈 되지 못한다면 메모리 문제가 발생할 수 있다. 이는 파일시스템에서 동작하는 어플리케이션의 경우 데이터를 파일에 저장시키지 못할 수도 있다.
  • 만약 객체가 autoreleased 된다면 그 객체는 어떤 스레드에 의해서도 해제될 수 있다. 이는 하나의 스레드에서만 처리 되어야 할 자원인 경우 문제를 일으킬 수 있다.

6. 콜렉션은 내부객체의 소유권을 획득

객체가 콜렉션(array, dictionary, set) 에 추가되면 콜렉션은 객체의 소유권을 갖는다. 콜렉션은 객체가 콜렉션에서 제거될 때와 콜렉션 자체가 릴리즈될 때 객체의 소유권을 반환한다. 따라서 콜렉션을 이용할 때 다음을 따라야한다.

// case 1
for(NSUInteger i=0; i<10 ; i++){

    NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
    [array addObject : convenienceNumber];

}

// case 2

for(NSUInteger i=0; i<10 ; i++){

    NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
    [array addObject : convenienceNumber];
    [allocedNumber release];
}

alloc 을 사용해서 생성된 객체라면 콜렉션에 추가후 release 해야하며, 그렇지 않고 간편생성자(convenience initializer)를 통해 생성된 객체라면 release 가 불필요하다.

* Reference : Practical Memory Management

Advertisements
Leave a comment

2 Comments

  1. UIAlertView, delegate 그리고 ARC « Park's Park
  2. [iOS] Automatic Reference Counting (ARC) | Park's Park

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: