iOS __bridge的那些事
本文是對《Objective-C高階程式設計》中__bridge
部分的整理,加上一部分自己的體會。
Objective-C 與 C語言之間的轉換
C語言的結構體(struct
或union
)成員中,如果存在Objective-C物件型變數,便會引起編譯錯誤。
struct Data { NSMutableArray *array; }
error: ARC forbids Objective-C objs in structs or unions NSMutableArray *array;
雖然是LLVM編譯器3.0,但不論怎樣,C語言的規約上沒有方法來管理結構體成員的生存週期。因為ARC把記憶體管理的工作分配給編譯器,所以編譯器必須能夠知道並管理物件的生存週期。例如C語言的自動變數(區域性變數)可使用該變數的作用域管理物件。但是對於C語的結構體成員來說,這在標準上就是不可實現的。
要把物件型變數加入到結構體成員中時,可強制轉換為void *
或是附加前面所述的__unsafe_unretained
修飾符。
struct Data { NSMutableArray __unsafe_unretained *array; }
如前所述,附有_unsafe_unretained修飾符的變數不屬於編譯器的記憶體管理物件。如果管理時不注意賦值物件的所有者,便有可能遭遇記憶體洩漏或程式崩潰。這點在使用時應多加註意。
顯式轉換id
和void *
在MRC下,像以下程式碼這樣將id
變數強制轉換成void *
變數並不會出問題。
// MRC下 id obj = [[NSObject alloc] init]; void *p = obj;
更進一步,將改void *
變數賦值給id
變數中,呼叫其例項方法,執行時也不會有問題。
// MRC下 id 0 = p; [o release];
但是以上程式碼在ARC下便會引起編譯錯誤
error: implicit conversion of an objective-C pointer to 'void*' is disallowed with ARC void *p = obj; ^ error: implicit conversion of a non-Objective-C pointer type 'void *' to 'id' is disallowed with ARC id o = p;
id
型或物件型變數賦值給void *
或者逆向賦值時都需要進行特定的轉換。如果只想單純地賦值,則可以使用 "__bridge
轉換"。
OC指標與void *
互相轉換
使用__bridge
#pragma mark - OC指標與void *互相轉換,使用 __bridge - (void)OCAndVoidUse__bridgeInARC { id obj = [[NSObject alloc] init]; void *p = (__bridge void *)obj; id o = (__bridge id)(p); }
像這樣,通過“__bridge
轉換”,id
和void *
就能夠相互轉換。但是轉換為void *
的__bridge
轉換,其安全性與賦值給__unsafe_unretained
修飾符相近,甚至會更低。如果管理時不注意賦值物件的所有者,就會因懸垂指標而導致程式崩潰。
注意:此時指標變數p
並沒有持有物件,因為__bridge
並不會改變持有情況。
OC指標轉換為void *
指標
ARC下使用__bridge_retained
#pragma mark - OC指標轉換為void *指標,ARC下使用 __bridge_retained - (void)OCToVoid__bridge_retainedInARC { void *p = 0; { id obj = [[NSObject alloc] init]; p = (__bridge_retained void *)obj; //p = (__bridge void *)obj; 報錯,obj出了作用域就會銷燬,__bridge不改變持有情況,所以p成為懸垂指標 } NSLog(@"class===%@",[(__bridge id)p class]); }
下面我們看看在MRC下原始碼是如何編寫的。
#pragma mark - OC指標轉換為void *指標,MRC下 - (void)OCToVoid__bridge_retainedInMRC { /* MRC下 */ void *p = 0; { id obj = [[NSObject alloc] init]; NSLog(@"obj retainCount===%lu",[obj retainCount]); /* [obj retainCount] -> 1 */ p = [obj retain]; NSLog(@"obj retainCount===%lu",[obj retainCount]); /* [obj retainCount] -> 2 */ [obj release]; NSLog(@"obj retainCount===%lu",[obj retainCount]); /* [obj retainCount] -> 1 */ } NSLog(@"(id)p retainCount===%lu",[(id)p retainCount]); /** * [(id)p retainCount] -> 1 * 即 * [obj retainCount] -> 1 * 物件仍然存在 */ NSLog(@"class===%@",[(__bridge id)p class]); }
-
__bridge_retained
轉換可使要轉換賦值的變數也持有所賦值的物件。 -
__bridge_retained
轉換變為了retain
。 -
變數
obj
和變數p
同時持有物件。 -
變數作用域結束時,雖然隨著持有強引用的變數
obj
失效,物件隨之釋放,但由於__bridge_retained
轉換使變數p
看上去處於持有該物件的狀態,因此該物件不會被廢棄。
void *
指標轉換為OC指標
ARC下使用__bridge_transfer
#pragma mark - void *指標轉換為OC指標,ARC下使用 __bridge_transfer - (void)VoidToOC__bridge_transferInARC { void *p = 0; { id tempObj = [[NSObject alloc] init]; p = (__bridge_retained void *)tempObj; } id obj = (__bridge_transfer id)p; }
在MRC下原始碼。
#pragma mark - void *指標轉換為OC指標,MRC下 - (void)VoidToOC__bridge_transferInMRC { void *p = 0; { id tempObj = [[NSObject alloc] init]; p = [tempObj retain]; [tempObj release]; } id obj = (id)p; /** * 同__bridge_retained轉換與retain類似,__bridge_transfer轉換與release相似。 * 在給id obj 賦值時retain即相當於__strong修飾符的變數。 */ [obj retain]; [(id)p release]; }
-
__bridge_transfer
轉換與__bridge_retained
相反,“被轉換的變數”所持有的物件在變數賦值給“轉換目標變數”後隨之釋放 -
同
__bridge_retained
轉換與retain
類似,__bridge_transfer
轉換與release
相似。在給id obj
賦值時retain
即相當於__strong
修飾符的變數。
Objective-C物件與Core Foundation物件之間的轉化
除了Objective-C與C語言之間的轉換之外,再介紹一下Objective-C物件與Core Foundation物件之間的轉化。
以下函式可用於Objective-C物件與Core Foundation物件之間的相互轉換,即Toll-Free Bridge(免費橋)轉換。
// After using a CFBridgingRetain on an NSObject, the caller must take responsibility for calling CFRelease at an appropriate time. NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) { return (__bridge_retained CFTypeRef)X; } NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) { return (__bridge_transfer id)X; }
將Core Foundation的物件轉換為OC物件來處理
使用__bridge
#pragma mark - 將Core Foundation的物件轉換為OC物件來處理,使用 __bridge static __weak id testPointer = nil; - (void)CoreFoundationToOC__bridge { // 建立一個作用域,目的是測試Core Foundation框架的物件會不會在作用結束後自動回收 { /** CFMutableArrayRef是CF框架下的型別,編譯器無法自動管理記憶體,也就是說系統不會主動釋放CFMutableArrayRef的變數,不手動釋放就會記憶體洩露 Core Foundation框架生成並持有物件,之後的物件引用計數為“1”。 */ CFMutableArrayRef cfMutableArr = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); // __bridge轉換不改變物件的持有情況 testPointer = (__bridge id)cfMutableArr; // testPointer = CFBridgingRelease(cfMutableArr); 報錯,因為CFBridgingRelease()會立即釋放cfMutableArr,弱指標立即置為nil // 作用域內列印引用計數 CFIndex count = CFGetRetainCount(cfMutableArr); NSLog(@"count===%ld",count);// count===1 } /** 作用域外列印仍然是1,可見作用域結束後並不能銷燬Core Foundation框架的物件,發生記憶體洩漏 */ CFMutableArrayRef cfTemp = (__bridge CFMutableArrayRef)testPointer; CFIndex count2 = CFGetRetainCount(cfTemp); NSLog(@"count2===%ld",count2);// count2===1 /** 方法作用域外列印引用計數 */ [self printOutOfMethodScope]; } // MARK:方法作用域外列印引用計數 - (void)printOutOfMethodScope { /** 雖然count3顯示是2,但是隻要呼叫CFRelease釋放一次,CFGetRetainCount()就會崩潰,因為實際上RetainCount是1,所以這應該是個系統bug:blush: CFIndex count3 = CFGetRetainCount((__bridge CFMutableArrayRef)(testPointer)); CFRelease((__bridge CFMutableArrayRef)testPointer); NSLog(@"count3===%ld",count3);// count3===2 NSLog(@"count4===%ld",CFGetRetainCount((__bridge CFMutableArrayRef)(testPointer)));// 崩潰 */ // 在方法作用域外列印引用計數仍然是“1”,可見cfMutableArr如不妥善管理,極易造成記憶體洩露 CFMutableArrayRef cfTemp = (__bridge CFMutableArrayRef)testPointer; CFIndex count3 = CFGetRetainCount(cfTemp); NSLog(@"count3===%ld",count3);// count3===1 }
同OC與void *
之間一樣,__bridge
可以實現Core Foundation物件與OC物件相互轉換,但是__bridge
仍然不改變持有情況。
將Core Foundation的物件轉換為OC物件來處理
使用CFBridgingRelease()
或__bridge_transfer
#pragma mark - 將Core Foundation的物件轉換為OC物件來處理,使用CFBridgingRelease或__bridge_transfer static __weak id testPointer2 = nil; - (void)CoreFoundationToOC__bridge_transferAndCFBridgingRelease { { CFMutableArrayRef cfMutableArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); /** *Core Foundation框架的API生成並持有物件,之後的物件引用計數為“1”。 */ NSLog(@"RetainCount===%ld",CFGetRetainCount(cfMutableArray));// RetainCount===1 /** *通過CFBridgingRelease賦值,變數obj持有物件強引用的同時,cfMutableArray指標對於物件的強引用通過CFBridgingRelease釋放。 */ id obj = CFBridgingRelease(cfMutableArray); // 或者id obj = (__bridge_transfer id)cfMutableArray; // 用testPointer2這個弱指標來跟蹤obj,在出了作用域後最終是否被釋放 testPointer2 = obj; NSLog(@"weak===%@",testPointer2); // weak===( ) // 因為只有obj持有物件的強引用,故引用計數為“1”。 NSLog(@"RetainCount after the cast===%ld",CFGetRetainCount(cfMutableArray));// RetainCount after the cast===1 // 另外,因為經由CFBridgingRelease轉換後,賦值給cfMutableArray中的指標也指向仍然存在的物件,所以可以正常使用。 NSLog(@"class===%@",[obj class]);// class===__NSCFArray } // 出了作用域後obj就立即被釋放了,所以弱指標testPointer2才會為nil NSLog(@"weak after the cast===%@",testPointer2);// weak after the cast===(null) }
CFBridgingRelease()
內部實現就是__bridge_transfer
,類似release
,原Core Foundation物件會被立即釋放。賦值給OC物件後,編譯器會自動管理記憶體。
將OC物件轉換為Core Foundation的物件來處理
使用__bridge
#pragma mark - 將OC物件轉換為Core Foundation的物件來處理,使用 __bridge - (void)OCToCoreFoundation__bridge { CFMutableArrayRef cfMutableArr = NULL; { // 變數obj持有對生成物件並持有物件的強引用 id obj = [[NSMutableArray alloc] init]; /** *因為__bridge轉換不改變物件的持有狀況, *所以只有通過變數obj的強引用, *引用計數為“1”。 */ cfMutableArr = (__bridge CFMutableArrayRef)obj; CFShow(cfMutableArr); NSLog(@"RetainCount===%ld",CFGetRetainCount(cfMutableArr)); } /** * 因為變數obj超出其作用域, * 所以其強引用失效,物件得到釋放, * 無持有者的物件被廢棄。 */ /** * 此後對物件的訪問出錯!(懸垂指標) */ NSLog(@"RetainCount after the scope===%ld",CFGetRetainCount(cfMutableArr)); CFRelease(cfMutableArr); }
__bridge
不改變物件的持有狀況,所以OC物件obj
在出了作用域被釋放後,cfMutableArr
變成了懸垂指標。
將OC物件轉換為Core Foundation的物件來處理
使用CFBridgingRetain()
或__bridge_retained
#pragma mark - 將OC物件轉換為Core Foundation的物件來處理,使用CFBridgingRetain或__bridge_retained - (void)OCToCoreFoundation__bridge_retainedAndCFBridgingRetain { CFMutableArrayRef cfMutableArr = NULL; { // 變數obj持有對生成物件並持有物件的強引用 id obj = [[NSMutableArray alloc] init]; /** *通過CFBridgingRetain或者__bridge_retained, *將物件CFRetain, *賦值給變數cfMutableArr */ cfMutableArr = (__bridge_retained CFMutableArrayRef)obj;// 或者CFBridgingRetain(obj) /** * 通過obj的強引用和 * 通過__bridge_retained, * 引用計數為“2” */ CFShow(cfMutableArr); NSLog(@"RetainCount===%ld",CFGetRetainCount(cfMutableArr)); // RetainCount===2 } /** * 因為變數obj超出其作用域,所以其強引用失效, * 引用計數為“1” */ NSLog(@"RetainCount after the scope===%ld",CFGetRetainCount(cfMutableArr)); CFRelease(cfMutableArr); /** * 因為將物件CFRelease,所以其引用計數為“0” * 故該物件被廢棄。 */ }
CFBridgingRetain()
內部實現就是__bridge_retained
,類似retain
,Core Foundation物件cfMutableArr
會持有OC物件。編譯器不會自動管理Core Foundation物件的記憶體,需要呼叫CFRelease ()
手動釋放。
總結
-
__bridge
可以實現Objective-C與C語言變數 和Objective-C與Core Foundation物件 之間的互相轉換 -
__bridge
不會改變物件的持有狀況 ,既不會retain
,也不會release
-
__bridge
轉換需要慎重分析物件的持有情況,稍不注意就會記憶體洩漏 -
__bridge_retained
用於將OC變數轉換為C語言變數 或將OC物件轉換為Core Foundation物件 -
__bridge_retained
類似於retain
,“被轉換的變數”所持有的物件在變數賦值給“轉換目標變數”後持有該物件 -
__bridge_release
用於將C語言變數轉換為OC變數 或將Core Foundation物件轉換為OC物件 -
__bridge_release
類似於release
,“被轉換的變數”所持有的物件在變數賦值給“轉換目標變數”後隨之釋放