[Effective Objective-C] #49 커스텀 메모리 관리 시맨틱을 가진 컬렉션을 만들기 위해 무비용 전환을 사용하라
출처 : Effective Objective-C
-
NSArray 는 배열을 위한 Foundation 의 오브젝티브-C 클래스이다.
그리고 CFArray 는 CoreFoundation 의 배열을 위한 것이다.
배열을 생성하는 이 두 가지 방법은 별개로 보이지만 무비용 전환이라는 강력한 기능 덕에 이 두 클래스 간 캐스팅을 아주 긴밀하게 할 수 있다.
-
무비용 전환으로 Foundation 의 오브젝티브-C 클래스와 CoreFoundation 의 C 데이터 구조체 간에 캐스팅을 할 수 있다.
나는 C 수준 API를 클래스나 객체가 아닌 데이터 구조체라고 부른다.
그것들은 오브젝티브-C 의 클래스나 객체와 동일하지 않기 때문이다.
예를 들어 CFArray 는 CFArrayRef 에 의해 참조된다.
이는 __CFArray 구조체를 가리키는 포인터다.
배열 크기를 얻기 위해 CFArrayGetCount 같은 함수를 이용해 구조체를 조작한다.
-
간단한 무비용 전환 예제는 다음과 같다.
NSArray *anNSArray = @[@1, @2, @3, @4];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@“Size of array = %li”, CFArrayGetCount(aCFArray));
-
캐스팅 안에 있는 __bridge 는 캐스팅하는 오브젝티브-C 객체를 어떻게 다루어야 할지 ARC 에게 알려준다.
__bridge 가 뜻하는 것은 ARC 가 여전히 오브젝티브-C 객체의 소유권을 가지고 있음을 말한다.
반면에 __bridge_retained 는 ARC 가 객체 소유권을 넘겨주는 것을 의미한다.
__bridge 가 사용된 이전 예에서는 배열 사용이 끝난 후 CFRelease(aCFArray)를 추가해야 할 의무가 없다.
반대로 캐스팅하려면 __bridge_transfer 를 쓰면 된다.
CFArrayRef 를 NSArray* 로 캐스팅하고 ARC 가 객체의 소유권을 갖길 원한다면 이 캐스팅 타입을 사용하면 된다.
이 세 가지 캐스팅 타입은 전환 캐스트(bridged cast)라고 한다.
-
Foundation 의 오브젝티브-C 클래스는 CoreFoundation 의 C 데이터 구조체가 할 수 없는 어떤 일을 할 수 있다.
반대의 경우도 마찬가지다.
예를 들어 Foundation 의 사전이 지닌 큰 문제 중 하나는 키가 복사되고 값이 리테인되는 것이다.
이 시맨틱은 무비용 전환의 힘을 사용하지 않고는 변경될 수 없다.
본래 NSMutableDictionary 가 기본 동작으로 키는 복사하고 값은 리테인하기에
키로 사용하길 원하는 객체가 복사되지 않는다면 다음과 같은 런타임 에러가 일어난다
*** terminating app due to uncaught exception
‘NSInvalidArgumentException’, resason: ‘-[EOCClass copyWithZone:]: unrecognized selector sent to instance 0x7fd19232
위의 에러는 클래스가 NSCopying 프로토콜을 지원하지 않는다는 뜻이다.
copyWithZone: 이 구현되지 않았기 때문이다.
CoreFoundation 수준으로 내려가서 사전을 생성하면 사전의 메모리 관리 시맨틱을 변경할 수 있다.
그러면 키를 복사하지 않고 리테인하는 사전을 생성할 수 있다.
예제는 아래와 같다.
const void* EOCRetainCallback(CFAllocatorRef allocator, const void *value){
return CFRetain(value);
}
void EOCReleaseCallback(CFAllocatorRef allocator, const void *value){
CFRelease(value);
}
CFDictionaryKeyCallBacks keyCallbacks = {
0,
EOCRetainCallback,
EOCReleaseCallback,
NULL,
CFEqual,
CFHash
};
CFDictionaryValueCallbacks valueCallbacks = {
0,
EOCRetainCallback,
EOCReleaseCallback,
NULL,
CFEqual
};
CFMutableDictionaryRef aCFDictionary = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &valueCallbacks);
NSMutableDictionary *anNSDictionary = (__bridge_transfer NSMutableDictionary*)aCFDictionary;
-
객체를 리테인하지 않는 배열과 집합도 비슷한 방법으로 만들 수 있다.
특정 객체를 리테인하는 배열은 리테인 순환을 발생시킬 수 있다.
그러나 이 시나리오에서 다른 방법으로 이를 더 좋게 해결할 수 있다는 점을 주목해야 한다.
객체를 리테인하지 않는 배열을 생성하는 것은 위험한 짓이다.
객체 중 하나가 할당 해제되었는데 여전히 배열이 그 객체를 가지고 있다면 그 객체에 접근하는 즉시 앱은 크래시된다.
기억할 점
-
무비용 전환은 Foundation 의 오브젝티브-C 객체와 CoreFoundation 의 C 데이터 구조체 간에 캐스팅을 할 수 있게 한다.
-
CoreFoundation 으로 내려가 컬렉션을 생성하면 컬렉션이 자신의 데이터를 다룰 때 사용되는 다양한 콜백을 정의할 수 있다.
무비용 전환을 이용해 캐스팅하면 커스텀 메모리 관리 시맨틱을 갖는 오브젝티브-C 컬렉션을 만들 수 있다.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Effective Objective-C] #51 initialize 와 load 메서드는 간결하게 만들라 (0) | 2017.10.19 |
---|---|
[Effective Objective-C] #50 캐시가 필요할 때 NSDictionary 보다는 NSCache 를 사용하라 (0) | 2017.10.18 |
[Effective Objective-C] 목차와 요약을 통해 한 눈에 알아보는 Effective Objective-C #41 ~ #48 (0) | 2017.10.16 |
[Effective Objective-C] #48 반복문에는 블록 열거를 사용하라 (0) | 2017.10.15 |
[Effective Objective-C] #47 시스템 프레임워크를 숙지하라 (0) | 2017.10.14 |
댓글