iOS引用轉換:Foundation與Core Foundation物件互相轉換(__CFString轉NSString,void *轉id等等)

陳滿iOS發表於2019-03-01

原始碼下載

下載地址:蘋果公開的原始碼在這裡可以下載,opensource.apple.com/tarballs/

例如,其中,有兩個比較常見需要學習原始碼的下載地址:

當然,如果你想在github上線上檢視原始碼,可以點這裡:runtimeCoreFoudation

為什麼需要了解 引用轉換

例如,在用到runtime的關聯物件API的時候,可能見到過這種程式碼:

static NSString * const kCMkvoClassPrefix_for_Block = @"CMObserver_";

NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge void *)kCMkvoAssiociateObserver_for_Block);
複製程式碼

需要通過 (__bridge void *) 轉換 id 和 void * 。為什麼轉換?這是因為objc_getAssociatedObject的引數要求的。先看一下它的API:

objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
複製程式碼

對比一下兩個引數:

  • const void * _Nonnull key
  • static NSString * const kCMkvoClassPrefix_for_Block = @"CMObserver_";

那麼,想把NSString的字串轉成void *型別引數,必須進行引用轉換。那麼轉換的是什麼?OC中經常要對兩個框架的物件進行轉換:Foundation與Core Foundation物件。

至於上面的程式碼,完整的功能可查閱 iOS開發·KVO用法,原理與底層實現: runtime模擬實現KVO監聽機制

1. 兩個框架的基本知識

1.1 Foundation

框架名是Foundation.framework,在Xcode新建工程時可以選擇匯入(其實會預設自動依賴好)。Foundation框架允許使用一些基本物件,如數字和字串,以及一些物件集合,如陣列,字典和集合,其他功能包括處理日期和時間、記憶體管理、處理檔案系統、儲存(或歸檔)物件、處理幾何資料結構(如點和長方形)。這個框架中的類都是一些最基礎的類。來自於這個框架的類名以NS開頭。

iOS引用轉換:Foundation與Core Foundation物件互相轉換(__CFString轉NSString,void *轉id等等)

Foundation框架提供了非常多好用的類, 比如:

NSString : 字串
NSArray : 陣列
NSDictionary : 字典
NSDate : 日期
NSData : 資料
NSNumber : 數字
複製程式碼

1.2 Core Foundation

Core Foundation 物件主要使用在用C語言編寫的Core Foundation 框架中,並使用引用計數的物件。在ARC無效時,Core Foundation 框架中的retain/release 分別是 CFRetain /CFRelease。

框架CoreFoundation.framework是一組C語言介面,它們為iOS應用程式提供基本資料管理和服務功能。下面列舉該框架支援進行管理的資料以及可提供的服務。查閱Core Foundation的完整API 點這裡

  • CF的引用定義:CFStringRefCFArrayRef
    查閱CFArrayRef 的定義 點這裡
    查閱CFStringRef 的定義 點這裡
typedef const struct __CFString * CFStringRef;
typedef const struct __CFArray * CFArrayRef;
複製程式碼
  • CF的原始碼:__CFString__CFArray
    查閱CF中結構體的原始碼 點這裡
iOS引用轉換:Foundation與Core Foundation物件互相轉換(__CFString轉NSString,void *轉id等等)
  • 這些結構體的定義如下:
CFArray.c
struct __CFArray {
    CFRuntimeBase _base;
    CFIndex _count;		/* number of objects */
    CFIndex _mutations;
    int32_t _mutInProgress;
    __strong void *_store;           /* can be NULL when MutableDeque */
};
複製程式碼
CFString.c
struct __CFString {
    CFRuntimeBase base;
    union {	// In many cases the allocated structs are smaller than these
	struct __inline1 {
	    CFIndex length;
        } inline1;                                      // Bytes follow the length
	struct __notInlineImmutable1 {
	    void *buffer;                               // Note that the buffer is in the same place for all non-inline variants of CFString
	    CFIndex length;                             
	    CFAllocatorRef contentsDeallocator;		// Optional; just the dealloc func is used
	} notInlineImmutable1;                          // This is the usual not-inline immutable CFString
	struct __notInlineImmutable2 {
	    void *buffer;
	    CFAllocatorRef contentsDeallocator;		// Optional; just the dealloc func is used
	} notInlineImmutable2;                          // This is the not-inline immutable CFString when length is stored with the contents (first byte)
	struct __notInlineMutable notInlineMutable;
    } variants;
};
複製程式碼

1.3 兩者關係

Core Foundation 框架和 Foundation 框架緊密相關,它們為相同功能提供介面,但 Foundation 框架提供Objective-C介面。Foundation物件 和 Core Foundation物件間的轉換,俗稱為橋接。如果您將Foundation 物件和 Core Foundation 型別摻雜使用,則可利用兩個框架之間的 “ Toll Free Bridging”。所謂的Toll-free bridging是說您可以在某個框架的方法或函式同時使用 Core Foundation 和 Foundation 框架中的某些型別。

很多資料型別支援這一特性,其中包括群體和字串資料型別。每個框架的類和型別描述都會對某個物件是否為 Toll-free bridged,應和什麼物件橋接進行說明。如需進一步資訊,請閱讀 Core Foundation 框架參考

2. Objective-C指標與CoreFoundation指標之間的轉換

2.1 MRC下的轉換

  • CF–>OC
    強制轉換符:(CFStringRef)
  • OC–>CF
    強制轉換符:(NSString *)
  • 例子
-(void)bridgeInMRC {
    // 將Foundation物件轉換為Core Foundation物件,直接強制型別轉換即可
    NSString *strOC1 = [NSString stringWithFormat:@"xxxxxx"];
    CFStringRef strC1 = (CFStringRef)strOC1;
    NSLog(@"%@ %@", strOC1, strC1);
    [strOC1 release];
    CFRelease(strC1);
    
    // 將Core Foundation物件轉換為Foundation物件,直接強制型別轉換即可
    CFStringRef strC2 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);
    NSString *strOC2 = (NSString *)strC2;
    NSLog(@"%@ %@", strOC2, strC2);
    [strOC2 release];
    CFRelease(strC2);
}
複製程式碼

2.2 ARC下的轉換

ARC僅管理Objective-C指標(retain、release、autorelease),不管理CoreFoundation指標。CF指標由人工管理,手動的CFRetain和CFRelease來管理。

在ARC中,CF和OC之間的轉化橋樑是 __bridge,有3種方式:

  • __bridge 只做型別轉換,不改變物件所有權,是我們最常用的轉換符。
  • __bridge_transfer:ARC接管 管理記憶體
  • __bridge_retained:ARC釋放 記憶體管理

2.3 簡單互相轉換:__bridge

① 從OC轉CF,ARC管理記憶體:

  • (__bridge CFStringRef)
  • 需要人工CFRetain,否則,Cocoa指標釋放後, 傳出去的指標則無效。
  • 例子
- (void)viewDidLoad  
{  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge CFStringRef)aNSString;  
      
    (void)aCFString;  
}
複製程式碼

上面只是單純地執行了型別轉換,沒有進行所有權的轉移,也就是說,當aNSString物件被ARC釋放的時候,aCFString也不能被使用了。

② 從CF轉OC,需要開發者手動釋放,不歸ARC管:

  • (__bridge NSString *)
  • 需要人工CFRelease,否則,OC物件的指標釋放後,物件引用計數仍為1,不會被銷燬。
  • 例子
- (void)viewDidLoad  
{  
    [super viewDidLoad];  
      
    CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);  
    NSString *aNSString = (__bridge NSString *)aCFString;  
      
    (void)aNSString;  
      
    CFRelease(aCFString);  
}  
複製程式碼

3. ARC下記憶體管理髮生改變的轉換

3.1 CF–>OC:__bridge_transfer

  • 例子
- (void)viewDidLoad  {  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;  
    aNSString = (__bridge_transfer NSString *)aCFString;  
}  
複製程式碼

3.2 OC–>CF:__bridge_retained

  • 例子
- (void)viewDidLoad  {  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;  
      
    (void)aCFString;  
      
    //這時候,即使開啟ARC,也需要手動執行CFRelease  
    CFRelease(aCFString);   
}  
複製程式碼

3.3 怎麼區分記憶?

因為ARC無法管理CF物件的指標,所以,無論是CF轉OC還是OC轉CF,我們只需關心CF物件的引用需要加1還是減1即可。

  • CF轉OC:CFRef必須減1

這樣原來的CF物件就被釋放,所以,以後也不用手動釋放。

NSString   *c = (__bridge_transfer NSString*)my_cfref; // -1 on the CFRef   
複製程式碼
  • OC轉CF:CFRef 必須加1

這樣新的CF物件就不會被釋放,所以,以後用完必須手動釋放。

CFStringRef d = (__bridge_retained CFStringRef)my_id;  // returned CFRef is +1  
複製程式碼
//這時候,即使開啟ARC,CF物件用完後也需要手動執行CFRelease  
CFRelease(aCFString); 
複製程式碼

3.4 轉換相關的巨集

  • CFBridgingRetain
NS_INLINE CFTypeRef CFBridgingRetain(id X) {   
    return (__bridge_retain CFTypeRef)X;   
}   
複製程式碼
  • CFBridgingRelease
NS_INLINE id CFBridgingRelease(CFTypeRef X) {   
    return (__bridge_transfer id)X;   
} 
複製程式碼

例1

下面兩個等效

CFStringRef cfStr = (__bridge_retained CFStringRef)ocStr;  
複製程式碼
CFStringRef cfStr = CFBridgingRetain(ocStr);  
複製程式碼

例2

下面兩個等效

NSString *ocStr = (__bridge_transfer NSString*)cfStr;
複製程式碼
NSString *ocStr = CFBridgingRelease(cfStr);
複製程式碼

3.5 總結

  • CF轉化為OC時,並且物件的所有者發生改變,則使用CFBridgingRelease()__bridge_transfer
  • OC轉化為CF時,並且物件的所有者發生改變,則使用CFBridgingRetain()__bridge_retained

相關文章