[Objective-C] 오토릴리즈 풀

오토릴리즈 풀

오토릴리즈풀은 연기된 release 메시지를 보내는 매커니즘이다. 이는 객체를 양도하되 즉시 해제되지 않기를 원할 때 사용하는 방법이다. (예를 들면 메소드를 통해서 생성된 객체가 이런 경우에 해당된다.) 보통의 경우에는 오토릴리즈 풀을 생성할 필요가 없지만, 이렇게 해야하는 경우나 이렇게 하는 편이 좋은 경우는 있을 수 있다.

오토릴리즈 풀에 관해

오토릴리즈 풀은 NSAutoreleasePool의 인스턴스로 autorelease 메시지를 받은 객체들이 모이는 풀이다.1 이곳에 모인 객체들은 오토릴리즈 풀이 메모리에서 해제될 때 release 메시지를 받게 된다. 오토릴리즈 풀은 다른 오토릴리즈풀을 담을 수 있는 것처럼 보이는데, 실질적으로는 이들은 스택처럼 동작한다. 만약 새로운 오토릴리즈 풀을 생성한다면 이는 기존의 오토릴리즈 풀 내에 포함되는 것이 아니라 오토릴리즈 풀 스택의 맨 위에 추가된다. 만약 어떤 객체가 autorelease 메시지를 받는다면, 이 객체는 최상위의 오토릴리즈 풀에 들어가게 된다.

Objective-C 2.0 규격에서는 명시적으로 AutoreleasePool 객체를 만들기 보다는 오토릴리즈 풀이 필요한 곳을 @autoreleasepool{ ... } 블럭으로 감싼다. 특히 ARC를 적용하는 프로젝트에서는 명시적으로 오토릴리즈 풀 객체를 만들 수 없다.

코코아는 기본적으로 하나의 오토릴리즈 풀이 사용될 수 있다고 가정한다. 만약 사용가능한 오토릴리즈 풀이 없다면 오토릴리즈 객체는 release 메시지를 받지 못하고 메모리릭이 발생하게 된다. 따라서 풀이 없는 상황에서 autorelease 메시지를 보내면 코코아는 에러를 띄운다. 앱킷이나 UI킷 프레임워크는 각각의 이벤트 루프의 시작점에서 자동적으로 풀을 생성한다. 하지만 다음의 경우에는 별도의 오토릴리즈 풀을 만들어야 한다.

  1. 명령줄 프로그램과 같이 UI킷 혹은 앱킷을 사용하지 않는 프로그램을 만들 때. 앱킷을 사용하지 않는 파운데이션 기반 프로그램의 템플릿은 main 함수에서 오토릴리즈구문을 명시해주고 있다.
  2. 많은 양의 임시 객체를 사용하는 루프를 쓸 때. 만약 루프 안에서 임시 객체를 만들어 쓸 때 루프 속에 오토릴리즈 풀을 만들면 이는 루프를 돌면서 사용하는 메모리의 최대치를 낮게 제어할 수 있다.
  3. 다른 스레드를 만들 때, 오토릴리즈 풀을 생성한다. 스레드가 실행되면 가능한 먼저 오토스레드 풀을 만들어야 한다. 그렇지 않으면 메모리가 누수될 여지가 생긴다

NSAutoreleasePool 객체를 만들 때는 alloc, init 을 통해 생성하고 폐기시에는 drain 메시지를 보낸다. 오토릴리즈 풀에 autorelease 나 retain을 할 경우에는 예외가 발생한다. 오토릴리즈 풀은 생성된 루프나 함수 내에서 폐기 되어야 한다. 또한 객체의 인스턴스 변수에 대해서는 오토릴리즈 풀을 생성할 필요도 없고, 보통 하지 않는다.

로컬 오토릴리즈 풀 사용하기

많은 프로그램에서 오토릴리즈되는 임시 객체를 생성한다. 이러한 객체는 풀이 비워질 때까지 메모리에 들어있게 되는데, 로컬 오토릴리즈 풀을 사용하면 이러한 임시 객체가 사용하는 메모리의 최대치를 제한할 수 있게 된다. 풀이 비워질 때 임시 객체들은 릴리즈되고, 이들 객체는 다시 해제되어 메모리 사용량을 줄여줄 수 있다.


NSArray *urls = <# file url의 배열 #>;

for (NSURL *url in urls) {
   @autoreleasepool{
        NSError *error = nil;
        NSString *fileContents = [[ initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error] autorelease]
            /* 이러한 객체를 처리한다 */
    }
}

만약 루프 내의 오토릴리즈 풀 내에서 생성한 객체를 오토릴리즈 풀을 비운 후에도 유지하고 싶다면 명시적으로 retain 한다. 이렇게 명시적으로 retain 한 객체는 오토릴리즈 풀을 비운 이후에 다시 autorelease 메시지를 보내 오토릴리즈 객체로 만들어서 외부로 반환할 수 있다.

오토 릴리즈 풀 내에서 다른 오토릴리즈 풀을 생성하는 경우 이 들은 “nested” 되었다기 보다는 스택에 쌓이는 방식처럼 동작한다. 이 오토릴리즈 풀 스택의 최상위에 있는 풀은 현재 오토릴리즈 풀이다. 만약 스택의 중간에 있는 오토릴리즈 풀을 비우면 그 위에 있는 모든 풀들도 비워진다.

오토릴리즈 객체가 사용되는 방식

오토릴리즈 풀은 오토릴리즈 객체를 자동으로 해제하기 위해 쓰인다. 오토릴리즈 객체들은 메모리를 할당하는 위치를 유지할 수 없는 객체들에 사용된다. 만약 어떤 함수 내에서 새로운 객체를 생성하여 이를 반환한다고 하면, 코드 상으로는 할당과 해제의 짝을 맞추가기 매우 곤란해진다.

C의 경우 함수내부에서 할당한 객체를 외부로 반환하고 나면 할당/해제의 짝을 코드 상으로는 검사하기가 매우 어려우므로 통상 메모리 할당은 함수 외부에서 하고, 할당된 메모리를 초기화하는 방식으로 함수들이 디자인된다.

오토릴리즈 객체는 이렇게 함수 내부에서 생성되어 리턴되는 객체를 위한 메모리 관리 트릭이라 할 수 있다. 보통 클래스의 팩토리 함수들은 오토릴리즈 되는 객체를 리턴한다. 만약 커스텀 클래스에서 팩토리함수를 작성한다면, 반드시 리턴하기 이전에 오토릴리즈를 해야 한다. 물론 ARC를 적용하는 경우라면 컴파일러가 이 구문을 알아서 추가해줄 것이다.


  1. 일종의 배열로 자신이 release될 때 모든 원소에게 release 메시지를 보낸다.