__bridge之谜转自(http://www.cnblogs.com/goodleixiao/articles/2517911.html)

__bridge只做类型转换,但是不修改对象(内存)管理权;

__bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease或者相关方法来释放对象;

__bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。



自 Xcode4.2 开始导入ARC机制后,为了支持对象间的转型,Apple又增加了许多转型用的关键字。这一讲我们就来了解其用法,以及产生的理由。

引子

我们先来看一下ARC无效的时候,我们写id类型转void*类型的写法:

1
2
id obj = [[NSObject alloc] init];
void *p = obj;

反过来,当把void*对象变回id类型时,只是简单地如下来写,

1
2
id obj = p;
[obj 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;
                ^

__bridge

为了解决这一问题,我们使用 __bridge 关键字来实现id类型与void*类型的相互转换。看下面的例子。

1
2
3
4
5
id obj = [[NSObject alloc] init];

void *p = (__bridge void *)obj;

id o = (__bridge id)p;

将Objective-C的对象类型用 __bridge 转换为 void* 类型和使用 __unsafe_unretained 关键字修饰的变量是一样的。被代入对象的所有者需要明确对象生命周期的管理,不要出现异常访问的问题。

除过 __bridge 以外,还有两个 __bridge 相关的类型转换关键字:

  • __bridge_retained
  • __bridge_transfer

接下来,我们将看看这两个关键字的区别。

__bridge_retained

先来看使用 __bridge_retained 关键字的例子程序:

1
2
3
id obj = [[NSObject alloc] init];

void *p = (__bridge_retained void *)obj;

从名字上我们应该能理解其意义:类型被转换时,其对象的所有权也将被变换后变量所持有。如果不是ARC代码,类似下面的实现:

1
2
3
4
id obj = [[NSObject alloc] init];

void *p = obj;
[(id)p retain];

可以用一个实际的例子验证,对象所有权是否被持有。

1
2
3
4
5
6
7
8
void *p = 0;

{
    id obj = [[NSObject alloc] init];
    p = (__bridge_retained void *)obj;
}

NSLog(@"class=%@", [(__bridge id)p class]);

出了大括号的范围后,p 仍然指向一个有效的实体。说明他拥有该对象的所有权,该对象没有因为出其定义范围而被销毁。

__bridge_transfer

相反,当想把本来拥有对象所有权的变量,在类型转换后,让其释放原先所有权的时候,需要使用 __bridge_transfer 关键字。文字有点绕口,我们还是来看一段代码吧。

如果ARC无效的时候,我们可能需要写下面的代码。

1
2
3
4
// p 变量原先持有对象的所有权
id obj = (id)p;
[obj retain];
[(id)p release];

那么ARC有效后,我们可以用下面的代码来替换:

1
2
// p 变量原先持有对象的所有权
id obj = (__bridge_transfer id)p;

可以看出来,__bridge_retained 是编译器替我们做了 retain 操作,而 __bridge_transfer 是替我们做了 release1

Toll-Free bridged

在iOS世界,主要有两种对象:Objective-C 对象和 Core Foundation 对象0。Core Foundation 对象主要是有C语言实现的 Core Foundation Framework 的对象,其中也有对象引用计数的概念,只是不是 Cocoa Framework::Foundation Framework 的 retain/release,而是自身的 CFRetain/CFRelease 接口。

这两种对象间可以互相转换和操作,不使用ARC的时候,单纯的用C原因的类型转换,不需要消耗CPU的资源,所以叫做 Toll-Free bridged。比如 NSArray和CFArrayRef, NSString和CFStringRef,他们虽然属于不同的 Framework,但是具有相同的对象结构,所以可以用标准C的类型转换。

比如不使用ARC时,我们用下面的代码:

1
2
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (CFStringRef)string;

同样,Core Foundation类型向Objective-C类型转换时,也是简单地用标准C的类型转换即可。

但是在ARC有效的情况下,将出现类似下面的编译错误:

    Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ (aka ‘const struct __CFString *’) requires a bridged cast
    Use __bridge to convert directly (no change in ownership)
    Use __bridge_retained to make an ARC object available as a +1 ‘CFStringRef’ (aka ‘const struct __CFString *’)

错误中已经提示了我们需要怎样做:用 __bridge 或者 __bridge_retained 来转型,其差别就是变更对象的所有权。

正因为Objective-C是ARC管理的对象,而Core Foundation不是ARC管理的对象,所以才要特意这样转换,这与id类型向void*转换是一个概念。也就是说,当这两种类型(有ARC管理,没有ARC管理)在转换时,需要告诉编译器怎样处理对象的所有权。

上面的例子,使用 __bridge/__bridge_retained 后的代码如下:

__bridge
1
2
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (__bridge CFStringRef)string;

只是单纯地执行了类型转换,没有进行所有权的转移,也就是说,当string对象被释放的时候,cfString也不能被使用了。

__bridge_retained
1
2
3
4
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (__bridge_retained CFStringRef)string;
...
CFRelease(cfString); // 由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release

使用 __bridge_retained 可以通过转换目标处(cfString)的 retain 处理,来使所有权转移。即使 string 变量被释放,cfString 还是可以使用具体的对象。只是有一点,由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release。

实际上,Core Foundation 内部,为了实现Core Foundation对象类型与Objective-C对象类型的相互转换,提供了下面的函数。

1
2
3
4
5
6
7
CFTypeRef  CFBridgingRetain(id  X)  {
    return  (__bridge_retained  CFTypeRef)X;
}

id  CFBridgingRelease(CFTypeRef  X)  {
    return  (__bridge_transfer  id)X;
}

所以,可以用 CFBridgingRetain 替代 __bridge_retained 关键字:

1
2
3
4
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = CFBridgingRetain(string);
...
CFRelease(cfString); // 由于Core Foundation不在ARC管理范围内,所以需要主动release。
__bridge_transfer

所有权被转移的同时,被转换变量将失去对象的所有权。当Core Foundation对象类型向Objective-C对象类型转换的时候,会经常用到 __bridge_transfer 关键字。

1
2
3
4
CFStringRef cfString = CFStringCreate...();
NSString *string = (__bridge_transfer NSString *)cfString;

// CFRelease(cfString); 因为已经用 __bridge_transfer 转移了对象的所有权,所以不需要调用 release

同样,我们可以使用 CFBridgingRelease() 来代替 __bridge_transfer 关键字。

1
2
CFStringRef cfString = CFStringCreate...();
NSString *string = CFBridgingRelease(cfString);

总结

由上面的学习我们了解到 ARC 中类型转换的用法,那么我们实际使用中按照怎样的原则或者方法来区分使用呢,下面我总结了几点关键要素。

  • 明确被转换类型是否是 ARC 管理的对象

    • Core Foundation 对象类型不在 ARC 管理范畴内
    • Cocoa Framework::Foundation 对象类型(即一般使用到的Objectie-C对象类型)在 ARC 的管理范畴内
  • 如果不在 ARC 管理范畴内的对象,那么要清楚 release 的责任应该是谁
  • 各种对象的生命周期是怎样的


1. 声明 id obj 的时候,其实是缺省的申明了一个 __strong 修饰的变量,所以编译器自动地加入了 retain 的处理,所以说 __bridge_transfer 关键字只为我们做了 release 处理

时间: 2025-01-17 21:54:16

__bridge之谜转自(http://www.cnblogs.com/goodleixiao/articles/2517911.html)的相关文章

Linux 上重启tomcat 【转】 http://www.cnblogs.com/tovep/articles/2473147.html

http://www.cnblogs.com/tovep/articles/2473147.html 在进行启动时报了权限不足的错误,使用sudo运行即可 [http://www.jb51.net/LINUXjishu/12713.html] Linux下Tomcat重新启动 在Linux系统下,重启Tomcat使用命令操作的! 首先,进入Tomcat下的bin目录 cd /usr/local/tomcat/bin 使用Tomcat关闭命令 ./shutdown.sh 查看Tomcat是否以关闭

sql之left join、right join、inner join的区别(转自:http://www.cnblogs.com/pcjim/articles/799302.html)

left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录inner join(等值连接) 只返回两个表中联结字段相等的行 举例如下: --------------------------------------------表A记录如下:aID aNum1 a200501112 a200501123 a200501134 a200501145 a20050115 表B记录如下:bID bNa

Python开发【第十七篇】:MySQL(一) http://www.cnblogs.com/wupeiqi/articles/5713315.html

http://www.cnblogs.com/wupeiqi/articles/5713315.html http://www.cnblogs.com/wupeiqi/articles/5713323.html http://www.cnblogs.com/wupeiqi/articles/5729934.html http://www.cnblogs.com/wupeiqi/articles/5716963.html 一.概述 1.什么是数据库 ? 答:数据的仓库,如:在ATM的示例中我们创建

tuxedo10g3R的安装过程(原文url:http://www.cnblogs.com/xianfish/articles/1955244.html)

RHEL 5.1 32Bit 安装配置TUXEDO 10R3 [前言]: 目的: 在Linux上安装配置Tuxedo 10R3 环境: RHEL 5.1 32Bit [前提条件]: 下载Tuxedo 10R3. Oracle是一个很**的公司,很多其旗下旧版本一点软件,比如oracle 8i/9i,weblogic 8在其官网都已经无法下载,Tuxedo旧一点的也没了,10R3版本的目前为止还有,下面简单提供一下下载方法: 1.登陆:https://edelivery.oracle.com/EP

urllib 模块 https://www.cnblogs.com/guishou/articles/7089496.html

1.基本方法 urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None) -         url:  需要打开的网址 -         data:Post提交的数据 -         timeout:设置网站的访问超时时间 直接用urllib.request模块的urlopen()获取页面,page的数据格式为bytes类型,需要

http://www.cnblogs.com/youfan/articles/3216816.html

我对 CodeFirst 的理解,与之对应的有 ModelFirst与 DatabaseFirst ,三者各有千秋,依项目实际情况自行选择. 1.开发过程中先行设计数据库并依此在项目中生成 *.dbml 或是 *.edmx 文件的,就是DatabaseFirst: 2.开发时先建立空的 *.edmx 文件,由此文件生成数据库的,就是ModelFirst: 3.使用 System.Data.Entity. DbContext 与 System.Data.Entity. DbSet构建数据模型,没有

C#获取文件夹/文件的大小以及占用空间 转摘自:http://www.cnblogs.com/chenpeng-dota/articles/2176470.html

C#获取文件夹/文件的大小以及占用空间 今天,头给了个任务:写个方法,我会给你个路径,计算这个路径所占用的磁盘空间 . 然后,找了很多资料.但大部分都是获取文件夹/文件的大小的.对于占用空间的没有成品代码.(ps:我没找到.)后来,在网上找了些资料,自己捣鼓出来了.在这里记录下,一则说不定以后能用到.再则如果有高手有更好的方法或者建议,求指点. 废话不多说了.begin. 首先说下文件夹/文件大小与占用空间的区别. 这个是硬盘分区格式有关 大小是文件的实际大小,而占用空间是占硬盘的实际空间 以F

jmeter压力测试报告-转载自http://www.cnblogs.com/atwanli/articles/4908475.html

目录 XXX压力测试报告... 1 一  测试内容... 2 二  测试方法... 2 三  测试目标... 2 四  测试环境... 2 五  系统部署... 3 5.1 物理部署... 3 5.2 网络访问... 3 六  性能测试结果与分析... 4 6.1 jmeter集群压测(5进程-每个进行10线程)... 4 6.2 jmeter集群压测(10进程-每个进行5线程)... 7 6.3 jmeter集群压测(10进程-每个进行10线程)... 11 七  结果汇总分析... 13 一

转载--Python之路,进程、线程、协程篇(原文地址:http://www.cnblogs.com/alex3714/articles/5230609.html)

本节内容 进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Queue队列 开发一个线程池 进程 语法 进程间通讯 进程池 进程与线程 什么是线程(thread)? 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 A