ObjectiveC stroage

From 흡혈양파의 인터넷工房
Jump to navigation Jump to search
xcode 4.2 ARC retain, weak, strong

출처
http://liehacker.goanygate.com/blog/?p=588


주로 걸리는 부분이 스토리보드와 ARC 관련된 부분이다.

오늘은 ARC와 관련해서 변경된 사항 일부를 언급하고자 한다.


ARC가 컴파일러단에서 reference count를 관리해주는 기능인만큼 이와 관련된 사항들이 많이 바뀌었는데 바로 retain, release, autorelease 등이다. 오늘 언급할 부분은 이 중에 property 선언시 사용되는 키워드에 관한 내용이다.


기존에 property를 선언할 때는 아래와 같이 사용하였다.

@property (nonatomic, retain) NSObject *obj;
@property (nonatomic, copy) NSString *str;

@property (nonatomic, assign) id<MyClassDelegate> delegate;


그러나 이제 ARC를 사용하게 되면 인스턴스들의 참조 카운트를 ARC가 관리를 하기 때문에 개발자들은 가급적 retain, release 등 참조 카운트를 변경시키는 키워드나 메서드를 사용하지 않도록 권하고있다.


그럼 이제는 property 선언 시 어떻게 해야 하는가?

우선 API 문서를 좀 보자


Instead of you having to remember when to use retain, release, and autorelease, ARC evaluates the lifetime requirements of your objects and automatically inserts the appropriate method calls for you at compile time.


즉, 개발자가 retain, release, autorelease를 언제 사용해야 하는지 기억할 필요 없이 ARC가 개발자가 생성한 객체에서 요구되는 라이프 타임을 평가하여 컴파일시에 자동으로 적절한 메서드 호출을 삽입시켜 준다는 것이다. (이전 글에도 적은 것 같은데…-.-?)


그러니까 retain은 다 빼버리자.


다시 API문서로 돌아가 올바른 클래스 선언의 예제를 보자

@interface Person : NSObject
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSNumber *yearOfBirth;
@property (nonatomic, strong) Person *spouse;
@end
 
@implementation Person
@synthesize firstName, lastName, yearOfBirth, spouse;
@end


새로운 키워드가 보인다. strong이라는 놈이 바로 그 놈이다. strong외에 weak도 있다.


다시 API문서로 가보면


// The following declaration is a synonym for: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
 
// The following declaration is similar to "@property(assign) MyClass *myObject;"
// except that if the MyClass instance is deallocated,
// the property value is set to nil instead of remaining as a dangling pointer.
@property(weak) MyClass *myObject;

요로케 설명이 되어있다.


strong은 그냥 retain과 같은 의미라고 생각하면 된다. 그러니까 기존에 property 선언시 사용했던 retain 키워드는 모두 strong으로 교체해 주면 된다.

weak의 경우 조금 설명이 더 붙어있는데 일단 weak는 기존의 assign과 같은 개념이지만 dealloc 되었을 경우 대기 포인터로 남아있지 않고 바로 nil값으로 처리된다는 점이 다르다고 한다.


일단 깊숙한 부분에 대해서는 아직 이해가 부족한 만큼 그냥 retain -> strong, assign -> weak로 대체해서 사용하면 될 것 같다.

이 때 다른 것들은 문제가 없는데 delegate의 property를 선언할 때 조금 문제가 발생하였다.

@property (nonatomic, assign) id<MyClassDelegate> delegate;

요렇게 코딩한 것을

@property (nonatomic, weak) id<MyClassDelegate> delegate;

요렇게 수정을 하였는데 .m파일의 @synthesize 선언부에서 다음과 같은 에러가 발생을 하였다.

Existing ivar 'delegate' for __weak property 'delegate' must be __weak


뭐가 문제일까 열심히 헤매다가 역시 구글링으로 답을 얻었다. 문제는 weak로 사용하기 위해서는 변수 타입에도 weak 형태로 사용하겠다는 선언을 해주어야 하는 것이었다.


즉, 변수 선언부에

id<MyClassDelegate> delegate;


라고 코딩한 부분을

__weak id<MyClassDelegate> delegate;

요렇게 앞쪽에 __weak라는 서술자를 하나 더 붙여주어야 하는 것이다.


이 것은 strong의 경우도 마찬가지이지만 __strong은 기본값이라서 명시적으로 붙이지 않아도 된다고 한다. 즉 아무 것도 붙어있지 않으면 자동으로 __strong으로 사용이 되는 것이다.


일단 strong은 retain과 거의 동일하니 그냥 쓰면 되겠지만 __weak의 경우는 주의할 것이 좀 있다고 한다.


또 API문서 인용이다~

NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
NSLog(@"string: %@", string);


이 경우 로그에 찍히는 내용은 'string: null'이 된다고 한다

첫번째 문장에서 string이 초기화되어 할당되기는 하지만 할당되는 시점에 strong으로 참조하는 곳이 없기 때문에 바로 dealloc되버리고 만다는 것이다.

뭔소린지 이해는 잘 안가지만 암튼 __weak의 경우 메모리를 절약할 수는 있겠지만 잘못 썼다가는 bad access에 시달리게 될 것 같다.


암튼 @property 선언시에는 이와 같은 내용에 주의해서 사용을 해야 한다는 것이 핵심이다.

더 자세한 내용은 문서에서 'Transitioning to ARC Release Notes'를 참고하기 바란다.


마지막으로 한 가지만 더 설명하고 마치도록 하자. 바로 Core Foundation 스타일의 변수를 Cocoa 스타일로 형변환시키는 내용이다.

우선 알아두어야 할 것은 ARC는 Core Foundation에서는 적용되지 않는다는 점이다. 즉, CFRetain과 CFRelease는 반드시 사용을 해주어야 한다.


본론으로 돌아와서 기존에는 아래와 같이 그냥 형 변환이 가능했다.

NSString *str = (NSString *)CFStringRef cfstr;


하지만 ARC를 사용하게 되면 컴파일러에게 오너쉽을 알려주기 위한 추가 코딩이 필요하다.


이 부분은 설명하기 어려워서 그냥 API 내용을 예시하는 것으로 갈음하겠다…-.-

id my_id;
CFStringRef my_cfref;
...
NSString   *a = (__bridge NSString*)my_cfref;     // Noop cast.
CFStringRef b = (__bridge CFStringRef)my_id;      // Noop cast.
...
NSString   *c = (__bridge_transfer NSString*)my_cfref; // -1 on the CFRef
CFStringRef d = (__bridge_retained CFStringRef)my_id;  // returned CFRef is +1