[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 参考

  1. objc_storeStrong: http://opensource.apple.com/source/objc4/objc4-647/runtime/NSObject.mm
  2. bridged casts: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#bridged-casts
  3. toll-free bridged types: https://developer.apple.com/library/ios/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html
时间: 2024-08-29 10:50:05

[iOS]深入理解__bridge - OC对象与C++对象的引用转换的相关文章

IOS开发之__bridge,__bridge_transfer和__bridge_retained

Core Foundation 框架 Core Foundation框架 (CoreFoundation.framework) 是一组C语言接口,它们为iOS应用程序提供基本数据管理和服务功能.下面列举该框架支持进行管理的数据以及可提供的服务: 群体数据类型 (数组.集合等) 程序包 字符串管理 日期和时间管理 原始数据块管理 偏好管理 URL及数据流操作 线程和RunLoop 端口和soket通讯 Core Foundation框架和Foundation框架紧密相关,它们为相同功能提供接口,但

我所理解的Remoting (2) :远程对象的生命周期管理[下篇]

在上一篇文章中([原创]我所理解的Remoting(2):远程对象生命周期的管理-Part I),我简要的讲述了CLR的垃圾回收机制和Remoting 基于Lease的对象生命周期的管理.在这篇文章中,我们将以此为基础,继续我们的话题.在文章的开始,我将以我的理解详细地讲述Remoting中两个重要的概念--Lease和Sponsorship.然后我通过一个Sample,为大家演示如何以不同的方法延长远程对象的生命周期. 我们先不谈远程对象.本地对象. 不管是远程的对象,还是本地对象,都对于程序

qq群: 281103593-请加入ios开发群(Oc)的QQ群: 281103593

问题描述 请加入ios开发群(Oc)的QQ群: 281103593 请加入ios开发群(Oc)的QQ群: 281103593;;;请加入ios开发群(Oc)的QQ群: 281103593 解决方案 好好好好,得得得得得,嘛嘛嘛嘛....加了

对Java的面对对象编程中对象和引用以及内部类的理解_java

最近利用下班的时候看了看的think in java感觉再看 和第一次看大不一样 接下来说一下java中对象和引用的关系,以及内部类的概念.1.java中一切都是对象 在java中是什么来操作者对象呢?答案是引用,这就好比C或者C++中的指针. 如果用拥有一个引用,那么此时你必须让其和一个对象关联在一起,否则这个引用并不会像你想象的那样任由你的控制,例如你创建了一个String的引用: String s ; 而此时并未与任何对象关联,如果此时你去做一些操作,如调用String的一些方法,肯定是会

jQuery对象与dom对象的转换[转]

最近在闲暇时间用jQuery搞了一个多文件上传的东东,顺便写点笔记. 一直以来对于通过jQuery方式获取的对象使不能直接使用JavaScript的一些方法的,开始的时候不理解,现在此案知道,原来jQuery获得的对象并不和我们平时使用getElementById获得的对象是一样的对象.所以一些新手就很迷惑,为什么${"#Element"}不能直接innerHTML,这就是原因所在,解决方式请看下文. jQuery对象与dom对象的转换 只有jquery对象才能使用jquery定义的方

Application对象与Session对象

上一讲中,我们学习了Request对象的Form数据集合.QueryString数据集合和ServerVariables数据集合.在继续下面的学习之前,建议你先轻松一下,因为接下来要介绍的Application对象相对比较抽象,刚开始理解起来恐怕会不辨东西.切记:当你找不到北时,便不要过多考虑Application对象到底是什么东西?还是一句老话,先学会使用它. 一.了解Application对象.为了提神,我们先看一个计数器的例程(你应该运行它以便于理解):先编辑一个wuf16.htm文件:<

Asp.ne response对象与request对象使用介绍

 这篇文章主要介绍了Asp.ne response对象与request对象使用,需要的朋友可以参考下 1.Response:服务器发给客户端信息,或者说是服务器的向用户发送输出结果.    Redirect:让客户端重新定向到指定的 URL.    Write:写出指定字符串.    2.request:客户端发给服务器,或者说是从客户端取得信息.    form:从使用post提交方式的表单获取表单元素的值.    querystring:取回查询字符串中的变量值,适用于get提交方式的表单.

css+div布局要素:文档流position属性 父级对象和同级对象

div+css布局要素:文档流position属性.父级对象和同级对象.从学div+css以来,一直对position几个属性的理解不够清晰.自己对position这一属性有了更深入和清晰的认识,同时让自己对整个div+css布局有了更深入的认识. 因为div实质是一个四方块,被很多业界人士形象的比喻成盒子.那么div+css布局的过程其实就是摆放这些盒子的过程.最近一周来,专门针对这个问题进行了深入的思考和研究.结果通过对这一问题的研究不仅让自己对position这一属性有了更深入和清晰的认识

实例:Cocos2d-js中使用纹理对象创建Sprite对象

本节我们会通过一个实例介绍纹理对象创建Sprite对象使用,这个实例如图5-2所示,其中地面上的草是放在背景(如下图所示)中的,场景中的两棵树是从后图所示的"树"纹理图片中截取出来的,图5-5所示是树的纹理坐标,注意它的坐标原点在左上角.  创建Sprite对象实例  场景背景图片 "树"纹理图片 "树"纹理图片 下面我们看看app.js 中HelloWorldLayer中初始化代码如下:  var HelloWorldLayer = cc.La