[iOS]深入理解__bridge-OC物件與C++物件的引用轉換
0x0 crash
昨天在iOS Geekers釘釘群裡聊到一個問題, 下面的程式碼會crash:
void* a = new char;
id ext = (__bridge id)a;
crash現場如圖:
看看掛的地方:
掛在objc_retain裡面:
objc_retain的作用是對oc物件做retain用的, 我們對指令做一下簡單的解析:
libobjc.A.dylib`objc_retain:
0x1810d00a0 <+0>: cbz x0, 0x1810d00c8 ; <+40> // 判斷x0也就是傳進來的第一個引數是不是nil, 在這裡x0是變數a, 也就是char型別的指標
0x1810d00a4 <+4>: tbnz x0, #63, 0x1810d00cc ; <+44> // 判斷x0暫存器的第63位是不是0
0x1810d00a8 <+8>: ldr x8, [x0] // 取x0指標的內容放入x8, 正常情況下這裡是oc物件的isa, 傳進來的並不是oc物件, 沒有isa. 這裡取出來的是0x0, 見下圖
0x1810d00ac <+12>: and x8, x8, #0xffffffff8 // x8`與操作`0xffffffff8(ISA_MASK), `與`完還是0
-> 0x1810d00b0 <+16>: ldrb w8, [x8, #32] // 取x8為基地址偏移量32的記憶體內容. 也就是x8+0x20, 也就是0+0x20=0x20. 0x20是一個保留地址不可讀寫, 直接掛!
0x1 解決
那麼問題來, 為什麼這裡會有一次retain操作導致掛掉呢? 看看程式碼:
id ext = (__bridge id)a;
id ext
, 這種寫法隱含了__strong id ext
, ext對a做了一次強引用, 而強引用就會對被引用的物件做一次retain.
那我們就不強引用就好了:
void* a = new char;
__unsafe_unretained id ext = (__bridge id)a;
那這樣就僅僅只是把a指標賦值給了ext指標, 並沒有做強引用不會觸發retain而導致crash, 不過a作為一個c++物件, 記憶體管理自己要做好!
0x2 用起來
看如下程式碼:
void test_bridge_parameter (id p0) {
}
void test_bridge() {
void* a = new char;
__unsafe_unretained id ext = (__bridge id)a;
test_bridge_parameter(ext);
}
這裡我們把ext作為引數, 傳遞給了test_bridge_parameter. 你覺得這段程式碼能夠正確執行麼?
當然不能!!!
臥槽, 咋掛函式的第0行了? 看看除錯:
這裡呼叫了 objc_storeStrong, 而objc_storeStrong又呼叫了objc_retain, 也就是我們傳進來的c++物件ext又被retain了. 為什麼呢?
objc_storeStrong 有兩個引數 p0, p1, 作用是把p1賦給p0並強引用一次.
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
在函式呼叫的引數傳遞時, 會把傳進來的引數, 按照參數列裡的定義的屬性, 做相關的賦值.
void test_bridge_parameter (id p0)
裡面的id p0
, 同樣隱含__strong id p0
, 改成
void test_bridge_parameter (__unsafe_unretained id p0)
去掉引數賦值時的強引用即可.
0x3 CFTypeRef和OC物件的關係
前面的例子中 char類並不是一個CFTypeRef型別, 導致並不能被轉換為OC物件:
/* Base "type" of all "CF objects", and polymorphic functions on them */
typedef const CF_BRIDGED_TYPE(id) void * CFTypeRef;
typedef const struct CF_BRIDGED_TYPE(NSString) __CFString * CFStringRef;
那麼CFTypeRef型別的物件是怎麼轉為OC物件的呢?
來一段程式碼:
void test_bridge() {
CFStringRef helloCF = CFSTR("hello, world");
}
反彙編:
testbridge`test_bridge:
0x10000ac78 <+0>: sub sp, sp, #16 ; =16
0x10000ac7c <+4>: adrp x8, 2
0x10000ac80 <+8>: add x8, x8, #32 ; =32
0x10000ac84 <+12>: str x8, [sp, #8]
-> 0x10000ac88 <+16>: add sp, sp, #16 ; =16
0x10000ac8c <+20>: ret
bridge一下, 為了避免__strong帶來的多餘指令, 這裡用__unsafe_unretained來避免:
void test_bridge() {
CFStringRef helloCF = CFSTR("hello, world");
__unsafe_unretained NSString *helloNS = (__bridge id)helloCF;
}
反彙編:
testbridge`test_bridge:
0x1000f2c70 <+0>: sub sp, sp, #16 ; =16
0x1000f2c74 <+4>: adrp x8, 2
0x1000f2c78 <+8>: add x8, x8, #32 ; =32
0x1000f2c7c <+12>: str x8, [sp, #8]
0x1000f2c80 <+16>: ldr x8, [sp, #8]
0x1000f2c84 <+20>: str x8, [sp]
-> 0x1000f2c88 <+24>: add sp, sp, #16 ; =16
0x1000f2c8c <+28>: ret
對比兩段彙編, 會發現區別僅僅在與多出來兩調指令, 一條是把x8從棧偏移量8位置裡面弄出來, 另一條把x8扔到棧的偏移量0的位置, 僅僅只做了簡單的賦值, 而並沒有任何對資料進行任何的修改. 那不就意味著CFTypeRef和對應的OC型別的資料結構是一樣的?
圖中兩者的內容都是”hello, world”, 但是資料型別卻不同!
我們看看sp(棧)的偏移量0和偏移量8的內容, 先用reg re讀出sp的地址:
sp = 0x000000016fd13aa0
圖中高亮的位置, 可以看出, 偏移量0和偏移量8的兩個64位地址裡面存的內容是一模一樣的(指向string物件的指標). 因吹絲挺!!!
並不是所有的類都可以無損轉換, 只有toll-free bridged types 才可以
0x4 __bridge_transfer 和 __bridge_retained
授人以魚不如受人以漁, 大家自己動手看看彙編程式碼差別! (其實是我懶… -_-!!)
切彙編程式碼除錯的方法是: Xcode頂部導航 -> Debug -> Debug Workflow -> Always Show Disassembly
__bridge在llvm文件的說明如下:
A bridged cast is a C-style cast annotated with one of three keywords:
(__bridge T) op casts the operand to the destination type T. If T is a retainable object pointer type, then op must have a non-retainable pointer type. If T is a non-retainable pointer type, then op must have a retainable object pointer type. Otherwise the cast is ill-formed. There is no transfer of ownership, and ARC inserts no retain operations.
(__bridge_retained T) op casts the operand, which must have retainable object pointer type, to the destination type, which must be a non-retainable pointer type. ARC retains the value, subject to the usual optimizations on local values, and the recipient is responsible for balancing that +1.
(__bridge_transfer T) op casts the operand, which must have non-retainable pointer type, to the destination type, which must be a retainable object pointer type. ARC will release the value at the end of the enclosing full-expression, subject to the usual optimizations on local values.
0x5 參考
- objc_storeStrong: http://opensource.apple.com/source/objc4/objc4-647/runtime/NSObject.mm
- bridged casts: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#bridged-casts
- toll-free bridged types: https://developer.apple.com/library/ios/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html
相關文章
- 值物件與引用物件物件
- iOS引用轉換:Foundation與Core Foundation物件互相轉換(__CFString轉NSString,void *轉id等等)iOS物件
- 物件與物件引用的區別物件
- DOM物件與jquery物件的相互轉換物件jQuery
- jQuery物件與Dom物件的相互轉換jQuery物件
- [轉] jQuery物件與DOM物件之間的轉換jQuery物件
- 引用物件與例項物件物件
- jQuery物件與DOM物件之轉換jQuery物件
- jQuery 物件 與 原生 DOM 物件 相互轉換jQuery物件
- java物件與json物件間的相互轉換Java物件JSON
- jQuery物件與原生JS dom物件間的轉換jQuery物件JS
- C++中引用和匿名物件的理解和本質剖析C++物件
- 深入理解JVM——物件JVM物件
- 深入理解JavaScript物件JavaScript物件
- 深入理解JVM虛擬機器-物件引用,GC與記憶體分配回收JVM虛擬機物件GC記憶體
- 在JavaScript中,DOM物件與jQuery物件的區別與轉換JavaScript物件jQuery
- 深入理解Java物件例項生成的例子!(轉)Java物件
- JavaScript引用物件的途徑(轉)JavaScript物件
- java 物件與xml相互轉換Java物件XML
- 物件的引用計數與dealloc物件
- iOS引用轉換:Foundation與Core Foundation對iOS
- 深入理解PHP物件注入PHP物件
- 深入分析JVM中的物件及引用(十六)JVM物件
- jquery物件和DOM物件的互相轉換jQuery物件
- c++物件的放置 (轉)物件
- json字串與物件互相轉換JSON字串物件
- 【JQuery】DOM物件和JQuery物件的互相轉換jQuery物件
- 深入理解ES6 --- 物件物件
- Golang 物件導向深入理解Golang物件
- 深入理解Java物件結構Java物件
- jxcel - 好用的Excel與Java物件轉換工具ExcelJava物件
- 深入理解Java中的不可變物件Java物件
- 理解物件以及物件的屬性、方法、事件 (轉)物件事件
- 如何將jquery生成的物件轉換成dom物件jQuery物件
- java基礎:深入理解Class物件與反射機制Java物件反射
- PHP物件的引用及物件優化策略PHP物件優化
- 物件賦值轉換物件賦值
- Java物件及物件引用變數Java物件變數