[iOS] 능력자의 블로그를 통해 되돌아본 ARC

즐겨찾기 해놓은 iOS 개발 블로그를 둘러보다 ARC에 관한 좋은 글을 발견하였다. 글 내용 중 본인이 몰랐던 또는 잊고 있었던 내용 중심으로 일부를 정리한다.  (원문 블로그 링크 )

이전에 본인이 포스팅한  [iOS] Automatic Reference Counting보다 훨씬 실용적인 내용이 많이 담겨져 있으므로 꼭 원문 블로그 링크의 글을 읽어보길 권한다.

Toll-Free Bridging

ARC는 Objective-C의 Object만을 대상으로 한다. Core Foundation과 같은 C/C++ 기반의 코드는  ARC 범주에서 벗어나 있다.

어플리케이션 개발에 Objective-C 클래스만을 이용할 수도 있겠지만, 때에 따라 Core Graphics와 같은 Core Foundation이 필요한 경우도 있다.

앞서 언급했듯이 Core Foundation은 ARC의 범주가 아니므로 Core Foundation API를 사용할 때는 개발자가 수동으로 메모리 관리를 해야한다. 그러나 다행히(?)도 Core Foundation 중 일부 클래스는 Objective-C  클래스로의 타입 캐스팅을 통해 Objectiv-C 객체처럼 사용가능하며 ARC에게 메모리 관리 책임을 맡길 수 있다. (이런 호환 가능한 클래스를 toll-free bridged type이라 한다. 리스트 참고)

Objective- C → Core Foundation이든 그 반대든, toll-free bridged type이 타입 캐스팅을 할 때는 compiler에게 메모리 관리 책임에 관한 정보를 알려줄 필요가 있는데 이때 사용하는 지시어가 __bridge, __bridge_transfer, __bridge_retained 이다.

1) __bridge

Objective-C Object인 NSObject 타입을 잠시동안만 Core Foundation 타입으로 변환하고 싶을 때 사용한다. ARC가 메모리 해지의 책임을 갖고 있다.


-(void)testMethodWithString:(NSString*)text{
...
... CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)text, NULL,  .....);
...
}

위 코드의 CFURLCreateStringByAddingPercenctEscapes 메소드의 두번째 파라미터는 CFStringRef 타입이고 이는 NSString과 호환되는 toll-free bridged type이다.

text는 testMethodWithString: 함수의 파라미터로 strong 성격을 지닌다. 함수의 시작점부터 끝날 때까지는 text 객체는 해지되지 않는 것을 보장받는다. Core Foundation API인 CFURLCreateStringByAddingPercenctEscapes를 사용하면서도 이러한 속성은 유지가 되어야 하며 CFStringRef로 변환되더라도 ARC가 text 객체의 메모리 관리 책임을 갖는다.

2) __bridge_transfer

Core Foundation에서 NSObject로 타입 캐스팅 하는 경우 사용한다. 보통 생성된 Core Foundation 객체는 CFRelease()를 이용하여 메모리 해지를 해줘야 하는데, NSObject 타입으로 바뀌었으니 메모리 해지는 ARC가 담당해라 라는 의미다.


NSString *temp = (__bridge_transfer NSString*)CFURLCreateStringByAddingEscapes(...);

CFURLCreateStringByAddingEscapes(…); 메소드는  CFStringRef타입을 반환한다. CFStringRef는 NSString로 타입 변환이 가능하며, 보통 NSString으로 다루는 것이 개발자에게 편리하다. NSString 타입으로 변환하여 반환하는데 이때 ARC에게 메모리 관리 책임은 이제 너라고 알려주는 역할을 __bridge_transfer가 한다.

CFBridgingRelease()라는 helper 함수가 존재하는데 위 코드는  NSString *temp = CFBridgingRelease(CFURLCreateStringByAddingEscapes(…))과 동일하다.

CFBridgingRelease라는 이름은 CFURLCreate…로 Core Foundation 객체를 생성했으니 이와 쌍으로 release 한다는 의미를 가진다. 보통 Create, Copy, Retain으로 명명된 Core Foundation 함수를 호출할 때 생성 객체가 toll-free bridged type이면 CFBridgingRelease()를 사용하면 된다.

3) __bridge_retained

NSObject 타입을 Core Foundation타입으로 캐스팅하는 경우 사용한다.  __bridge와 다른 것은 메모리 해지의 책임이 ARC로 유지되는 것이 아니라 ARC의 책임이 아니게 된다. 즉 Core Foundation 타입으로 변환된 객체에 대한 메모리 해지의 책임은 개발자에게 있으며 CFRelease()를 통해 수동으로 관리를 해야 한다.

...
CFStringRef s2 = (__bridge_retained CFStringRef)s1; // s1은 NSString 타입
...

CFRelease(s2); //명시적으로 메모리 해지 필요

CFBridgingRetain()이라는 helper 함수가 존재하는데 위 코드는 CFStringRef s2 = CFBridgingRetain(s1); 과 동일한 의미를 가진다. CFRelease()와 쌍을 맞추기 위해 Retain()이 들어갔다고 이해하면 된다.

__bridge는 Core Foundation간의 변환에만 사용되지는 않는다. 어떤 API는 void* 를 파라미터로 갖는 경우도 있다. void* 파라미터에는 Objective-C object, Core Foundation object등 어떠한 타입도 들어갈 수 있다는 의미다. (Objective-C 내에서 사용되는 id 타입과 헷갈리지 말자.)

// objective-c => void*
MyClass *myObject = [[MyClass alloc] init];
[UIView beginAnimations:nil context:(__bridge void*)myObject];
...

// *void -> objective-c
-(void)animationDidStart:(NSString*)animationid context:(void*)context{
...
MyClass *myObject = (__bridge MyClass*)context;
...

}

Strong

  • strong 속성을 지닌 property와 Instance variable을 사용할 경우, 변수가 더 이상 필요하지 않는 시점에 변수를 nil로 할당하는 것이 좋다. (개인적으로 자주 까먹는다. nil처리를 하지 않았다고 어플이 쉽게 죽거나 하진 않기 때문).
  • UIViewController 안에 strong 변수가 있는 경우 viewDidUnload() 메소드에서 nil 처리를 하면 UIViewController가 deallocated 되기 전에 불필요한 메모리 사용을 막을 수 있다.

read-only 속성

Property로 선언된 변수인 경우 self.name  같이 항상 Property 방식으로 접근하는 것이 좋다. property로 선언된 변수를 property’s backing instance ( .m 파일에서 프로퍼티 변수를 _name과 같은 형식으로 접근할 수 있다.)로 접근하는 경우는  init 메소드 또는 custom getter/setter 메소드로 한정하라.

@property (nonatomic, strong, readonly) NSNumber *temperature;

헤더파일에 위와 같이 readonly로 정의된 property인 경우, @implentation 에서 _temperature를 통하여 값을 변경한다면 ARC는 이상한 버그를 낼 수도 있다.

이 경우 좋은 솔루션은 header 파일의 선언은 그대로 두고 .m 파일에서 class extension을 이용하여 @property(nonatomic, strong, readwrite) NSNumber *temperature 로 재정의 하여 사용하는 것이다.

IBOutlet 

  • IBOutlet property에 권장하는 속성은 weak이다.
  • nib 파일에서 최상위 view(보통 self.view)가 IBOutlet으로 정의한 view를 소유하고 있으므로 weak 지시어로 생성하여도 무방하다.
  • IBOutlet을 weak으로 선언하는 가장 큰 이점은 viewDidUnload()메소드에서 nil처리를 하지 않아도 된다는 점이다. (최상위 view가 파괴될 때 자동으로 nil 처리가 된다. viewDidUnload시점엔  nil 이 되어 있다.)
  • iOS 디바이스가 low-memory warning을 받으면 view가 unload되는데 이때 weak으로 처리해야 IBOutlet에 연결된 view도 자동으로 해제된다.

그 밖의 정보 & Tip

  • LLVM 3.0 컴파일러를 도입하면서 .m 파일 (@implementation 내부)에 instance variable 설정이 가능해졌다.
  • MRC 에서 개발자가 @property(nonatomic, retain)을 즐겨 사용하는 이유는 메모리 관리를 편하게 하기 위해서였다. 프로퍼티에 retain 키워드를 넣음으로써 변수 할당때마다 retain (or copy) 키워드를 넣을 필요가 없다. ARC에서 이는 더이상 특별한 이점이 아니다. ARC에선 다른 클래스에서 사용할 경우에만 property를 이용하는 것이 좋다.
  • copy property : NSString 또는 NSArray에 copy property를 사용하는 경우가 많다. 이는 프로퍼티에 할당하더라도 기존의 값이 변경되지 않길 바라는 경우에 사용한다. copy를 하면 할당된 객체와 동일한 객체를 새로 만들어서 변수에 할당한다. copy는 strong과 동일한 성격을 지닌다.

== updated 2013/7/25  ==

확인해보니 iOS 6.0부터 viewDidUnload 메소드가 deprecated 되었다. iOS 6 부터는 low-memory 상태에서 viewDidUnload가 호출되지 않는다.

< from iOS developer library >

위에서 정리된 내용 중 viewDidUnload()가 언급된 곳은 IBOutlet 과 strong property 두 곳이다.

  •  IBOutlet : 권장 프로퍼티가 weak라는 것에는 문제는 없어 보인다. low-memory warning과 별개로 nib파일의 최상위 view에서 이미 IBOutlet으로 선언한 view를 소유하고 있으므로, UIViewController에서 다시 strong 속성으로 view를 소유하는 것은 효율적이지 않아 보인다.
  • strong 변수의 nil 처리 : viewDidUnload는 low-memory warning에서만 호출되도록 되어 있다. 그러나 더이상 low-memory 상태에서 호출되지 않으므로 didReceiveMemoryWarning()에서 현재 뷰에 사용되지 않는 strong 변수에 대하여 nil처리를 하여야 한다. strong 변수를 property로 선언하고 getter메소드에 nil인 경우를 처리해 놓는다면, didReceiveMemoryWarning()에서 nil로 할당된 변수가 다음에 사용될 경우 간편하게 처리될 수 있을 것 같다.
%d bloggers like this: