예전에 Xcode를 4.2로 업데이트한 후에 새로운 메뉴가 생겨서 이건 뭔가했었던 부분이 있었습니다. Use Automatic Reference Counting 메뉴인데 그냥 아 자동으로 객체들의 메모리를 관리해주는 아이구나 라고 생각만하고 지나갔었습니다.


불연듯 이게 뭔지 정확히 알아야 겠다는 생각이 들어 아침에 출근후 아이폰 개발자 사이트에서 레퍼런스를 읽어봤습니다. 아래는 그 내용을 정리한 내용입니다~ 영어가 짧아서 잘못 해석된 부분도 있을수도 있으니.. 이해해주시기 바랍니다 ㅋ


먼저 iOS Developer 사이트에 공개한 레퍼런스 링크를 알려드리겠습니다.

https://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html




1. ARC(Automatic Reference Counting)의 탄생


Automatic Reference Counting (이하 ARC)는 Objective-C에서 제공하는 자동 메모리 관리기능입니다. 이는 어플리케이션을 개발하는데 있어 Retain/Release에 대한 생각으로 개발하는데 있어 퍼포먼스를 낮추는 부분을 해소하기 위해 만들어 졌습니다.




2. ARC 요약


  • ARC의 역할은 컴파일시간에 메모리에 대한 관리를 추가하여 객체의 생존 시간을 필요한 만큼 보장하는데 있다. 다만 그 시간이 그렇게 긴시간은 아니다. 컨셉적으로 이는 이전의 메모리 관리 기법과 같다.
  • ARC는 Xcode 4.2 (Mac OS X v10.6, v10.7 - 64bit)와 iOS4, iOS5에서 사용가능하다. 단, Mac OS X v10.6과 iOS4에서는 Weak Reference 를 지원하지 않는다.




3. ARC 살펴보기


ARC는 기존에 retain, release, autorelease를 통해 메모리를 관리했던 것을 대신하여 자동적으로 생성한 객체에 대해 lifetime을 컴파일 시간에 평가하여 메모리를 관리한다. 컴파일러는 역시 적절한 위치에 dealloc메소드를 생성할 것이다.


아래의 코드를 보겠습니다.

@interface Person : NSObject

@property (nonatomic, strong) NSString *firstName;

@property (nonatomic, strong) NSString *lastName;

@property (nonatomic, strong) NSString *yearOfBirth;

@property (nonatomic, strong) Person *spouse;

@end


@implementation Person

@synthesize firstName, lastName, yearOfBirth, spouse

@end


(위에 나오는 strong 은 ARC의 새로운 속성으로 아래에서 다루도록 하겠습니다.)

위에 코드를 가지고 아래의 구현을 보겠습니다.

- (void)contrived

{

     Person *aPerson = [[Person alloc] init];

     [aPerson setFirstName:@""];

     [aPerson setLastName@""];

     [aPerson setYearOfBirth:[[NSNumber alloc] initWithInteger:2011];

}


위 코드를 보면 뻔하게 어느 부분에서 메모리 릭이 나는지 알 수 있습니다.

바로 Person *aPerson = [Person alloc] init]; 부분과 [[NSNumber alloc] initWithInteger:2011]; 부분입니다. 하지만 ARC를 사용한다면 별 문제가 되지 않습니다. 

왜냐하면 자동적으로 사용이 완료되면 release하여 사용한 메모리가 Dealloc되기 때문입니다. 

(Xcode 4.2이상에서 프로젝트를 생성할 때 Use Automatic Reference Counting를 선택하지 않은 상태에서 인스트리먼트를 돌려보고 다시 Use Automatic Reference Counting 를 체크하고 인스트리먼트를 돌려보면 메모리 릭에 대한 부분을 눈으로 확인 할 수 있습니다.)




3.1 ARC를 사용하는데 있어 새로운 Rules


  • dealloc 를 호출 할 수 없다. 또한 retain, release, retainCount, autorelease 역시 호출 할 수 없다. 물론 @selector(retain), @selector(release)의 작업 역시 금지된다. 특히 dealloc 메소드를 구현하는 경우 상위 클래스 호출은 컴파일러에 의해 수행되기 때문에 [super dealloc]을 호출할 필요가 없으며, 이 코드를 삽입하면 에러가 발생된다. 단, CFRetain, CFRelease 등과 같은 Core Foundation Style의 객체들은 사용이 가능하다. 
  • NSAllocationObject, NSDeallocationObject 를 사용할 수 없다.
  • C 구조체 내부에서 객체포인터를 사용할 수 없다. 대신 NSObject를 이용한 클래스를 사용한다.
  • id와 void *를 암시적으로 형변환 할 수 없다. 컴파일러에게 객체의 수명에 대해 알려줄 수 있는 특별한 형변환을 사용해야 한다. 
  • NSAutoreleasePool 객체를 사용 할 수 없다. 대신에 더 효율적인 @autoreleasepool 블록을 사용한다.
  • memory zones를 사용할 수 없다. 사용한다 하여도 런타임 시간에 무시된다.




3.2 ARC의 새로운 Lifetime 식별자소개


ARC애는 객체의 수명을 지정하는 새로운 몇가지 Lifetime을 갖는 식별자와 Zeroing weak reference가 제공된다. 약한 참조는 대상 객체의 수명에 영향을 주지 않지만 zeroing weak reference는 대상 객체가 해제되면 자동으로 nil값을 가지게 된다. ARC는 이전 참조 카운팅 방식에서 발생하던 strong reference cycles(Retain Cycle)문제를 방지해주지 않기 때문에 신중하게 사용해야 한다. 



3.2.1 Property Attributes


@property 속성에서 새로운 키워드인 strong, weak 키워드를 사용할 수 있다.

기존 참조 카운팅 방식에서 사용하던 아래의 코드는

@property (retain) MyClass *myObjecit;

ARC를 사용하여 아래와 같이 작성한다.

@property (strong) MyClass *myObject;


또한, 아래의 코드는

@property (assign) MyClass *myObject;

ARC를 사용하여 아래와 같이 작성한다.

@property (weak) MyClass *myObject

assign을 사용하는 경우 myObject의 대상 객체가 해제되면 댕글링포인터가 되지만, weak의 경우에는 nil로 설정된다. (zeroing weak reference)

*댕글링 포인터란, 이미 메모리에서 해제된 대상을 가리키고 있는 포인터를 말한다. 발생되는 원인은 동적으로 할당 받은 메모리가 해제되었는데도 포인터 변수가 해제된 메모리 주소를 가지고 있기 때문이다. 이를 방지하기 위해서는 메모리 해제 이후에 반드시 포인터 변수에 NULL값으로 초기화를 해준다.



3.2.2 새로운 Variable 키워드


__strong : 기본 값

__weak : zeroing weak reference. 대상 객체가 해제되면 nil이 된다.

__unsafe_unretained : weak reference. 대상 객체가 해제되면 댕글링 포인터가 된다.

_autoreleasing : id * 형식의 인수가 리턴시 자동적으로 해제되도록 지정된다.


함수의 내부에서 __weak를 사용 할 경우 해당 객체의 유효성에 주의해야 한다.

NSString __weak *string = [[NSString alloc] initWithFormat:@"Assigning retained object to weak variable; object will be released after assignment"];

위의 코드를 실제로 Xcode에 입력하면 Assigning retained object to weak variable; object will be released after assignment 라는 경고가 발생한다. 이는 weak변수에 retain된 객체를 할당하게 되면 해당 객체가 할당된 후에 해제된다는 것이다. 실제로 위의 코드를 로그로 출력해보면 null이 출력된다.


또한 Object의 참조변수를 넘길 때 역시 주의해야한다. 아래의 코드를 보자

NSError *error = nil;

BOOL ok = [myObject performOperationWithError:&error];

if(!ok)

{


}


하지만, 함축적 표현했을 뿐 본래 선언은 아래와 같다.

NSError *__strong e = nil;


그리고 메소드 역시 아래와 같이 선언되어 있다.

- (BOOL)performOperationNWithError:(NSError *__autoreleasing *)error;


그러므로 컴파일러는 위의 코드를 아래와 같이 다시 쓰게된다.

NSError *__strong error = nil;

NSError *__autoreleasing tmp = error;

BOOL ok = [myObject performOperationWithError:&tmp];

error = tmp;

if(!ok)

{


}

이러한 __strong의 지역변수 선언과 __autoreleasing 파라미터는 컴파일러가 스스로 임시변수를 만드는 현상을 야기한다. __strong 변수의 주소를 취할 경우 본래의 파라미터 포인터는 id __strong * 으로 선언하여 가져올 수 있다.



3.2.3 Use Lifetime Qualifiers to Avoid Strong Reference Cycles


strong reference cycle을 회피하기 위해 생존주기 수식자를 사용 할 수 있다. 예를 들면, 부모 자식 계층으로 나열된 객체의 그래프에서 부모 자식 사이에 상호 참조를 할 때, 부모에서 자식으로 strong reference를 하고 자식에서 부모로 weak reference를 하여 strong reference cycle을 회피하는 경우다. 다른 경우들은 좀 더 드문 경우인데 특히 block 객체를 포함할 경우이다. 

직접 reference count를 관리하는 모드에서, __block id x;와 같은 선언은 변수 x를 retain하지 않는 효과가 있다. 그러나 ARC모드에서 __ block id x; 는 기본적으로 x를 retain 한다. ARC모드에서 원래 __block modifier가 가진 의미대로 사용하기 위해서는 __unsafe_unretained __block id x;와 같이 사용해야 한다. 또한 __unsafe_unretained 라는 이름에서 알 수 있듯이 retain되지 않는 변수를 가지고 있는 것은 위험하고 따라서 권고되지 않는다. retain cycle을 깨기위한 좀 더 괜찮은 두가지 방법으로는 __weak와 __block 변수의 값을 nil로 설정하는 것이다.


다음코드는 직접 reference count를 수행하는 곳에서 종종 사용되는 패턴을 사용하여 이슈를 보여준다.

MyViewController *myController = [[MyViewController alloc] init...];

// ...

myController.completionHandler = ^(NSInteger result) {

     [myController dismissViewControllerAnimated:YES completion:nil];

};


[self presentViewController:myController animated:YES completion:^{

     [myController release];

}];


설명한 바와 같이 위와 같은 코드대신 __block 수식자를 사용하고, completion handler에서  myController 변수를 nil로 설정할 수 있다. 

MyViewController *__block myController = [[MyViewController alloc] init...];

// ...

myController.completionHandler = }^(NSInteger result) {

     [myController dismissViewControllerAnimated:YES completion:nil];

     myController = nil;

};


또는 __weak로 선언된 임시 변수를 사용 할 수 있다. 아래 코드는 이를 보여주고 있다.

MyViewController *myController = [[MyViewController alloc] init...];

// ...

MyViewController *__weak weakMyViewController = myController;

     myController.completionHandler = ^(NSInteger result) {

     [weakMyViewController dismissViewControllerAnimated:YES completion:nil];

};


그러나, 명백하지 않은 (non-trivial) cycle에서는 아래와 같은 코드를 사용해야 한다.

MyViewController *myController = [[MyViewController alloc] init...];

// ...

MyViewController *__weak weakMyController = myController;

myController.completionHandler = ^(NSInteger result) {

     MyViewController *strongMyController = weakMyController;

     if(strongMyController)

     {

          //...

          [strongMyController dismissViewControllerAnimated:YES completion:nil];

          //...

     }

     else

     {

          //Probably nothing...

     }

};

클래스가 __weak로 선언되는 것이 맞지 않는 경우 __unsafe_unretained를 사용할 수 있다. 그러나 이것은 포인터가 유요한지 또는 포인터가 같은 객체를 가르키고 있는지에 대한 검증이 어렵기 떄문에 nontrivial cycle에 비실용적일 수 있다.



3.2.4 ARC에서 사용하는 새로운 Autorelease Pools


ARC를 사용하는 경우 NSAutoReleasePool 클래스를 사용하여 autorelease pool에 직접 메시지를 보낼 수 없다. 

@autoreleasepool

{

     // 수많은 임시객체들을 생성하는 반복 구문과 같은 코드

}

이 구조는 컴파일러가 reference count 상태에 대해 알아낼 수 있도록 해준다. 이 구문의 시작점에서 새로운 autorelease pool이 push되고 제거될 때 pop이 되어진다. 기존 코드와의 호환성을 위해 autorelease pool이 제거될 때 예외가 발생하면 autorelease pool은 제거되지 않는다.



출처: http://j2enty.tistory.com/entry/iOS-ARCAutomatic-Reference-Counting-1 [무늬만 개발자 블로그.]