본문 바로가기
프로그래밍 놀이터/iOS

[Effective Objective-C] #49 커스텀 메모리 관리 시맨틱을 가진 컬렉션을 만들기 위해 무비용 전환을 사용하라

by 돼지왕 왕돼지 2017. 10. 17.
반응형

 [Effective Objective-C] #49 커스텀 메모리 관리 시맨틱을 가진 컬렉션을 만들기 위해 무비용 전환을 사용하라


출처 : Effective Objective-C

arc, arc 소유권, bridged cast, CFAllocatorRef, cfarray, CFArrayGetCount, CFArrayRef, CFDictionaryCreateMutable, CFDictionaryKeyCallBacks, CFDictionaryValueCallbacks, CFMutableDictionaryRef, CFRelease, copyWithZone, corefoundation, Foundation, key 복사 리테인하지 않는 dictionary, NSArray, nscopying, NSInvalidArgumentException, nsmutabledictionary, toll-free, unrecognized selector, [Effective Objective-C] #49 커스텀 메모리 관리 시맨틱을 가진 컬렉션을 만들기 위해 무비용 전환을 사용하라, _cfarray 구조체, __bridge, __bridge_retained, __bridge_transfer, 객체 리테인 하지 않는 배열, 메모리 관리 시맨틱, 무비용 전환, 배열, 소유권 이전, 컬렉션


-

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 컬렉션을 만들 수 있다.




반응형

댓글