32

我的一位朋友最近向我询问了在 ARC 下启用的新桥梁修改器。他问我是否知道在特定时间使用哪些,以及不同 __bridge 修饰符之间的区别是什么。他问我,“那么它们是如何工作的,我什么时候使用它们,我如何使用它们,以及它们是如何“在引擎盖下”工作的?

注意:这应该是“分享你的知识”类型的问题,我自己回答了这个问题,但我不确定我是否正确设置了它。

4

1 回答 1

66

因为我最近才知道它们是什么以及它们是如何运作的,所以我想与其他任何希望了解 ARC 下的 __bridge 修饰符的人分享,这可能会导致混淆,因为过去曾经完成免费桥接用一个简单的演员表。

过去,如果您想出于任何目的将 NSArray 对象转换为 CFArrayRef,则可以通过如下简单的转换来完成:

     NSArray* myArray = [NSArray alloc]initWithObjects:....]; //insert objects

     CFArrayRef arrayRef = (CFArrayRef) myArray;        

在这种情况下,也许你有一个颜色的 NSArray 并且需要将其转换为 CFArrayRef 以便与 CoreGraphics 一起使用来绘制渐变。

但是,对于 ARC,这将不再适用于您,您将收到此错误:

在此处输入图像描述

那么这到底是什么意思!?!

事实证明,ARC 不喜欢你做这种类型的演员,甚至会给你一些“修复它”的解决方案,它们似乎都有相同的__bridge关键字,所以让我们开始吧!

在 ARC 下,我们有 3 个主要的 __bridge 修饰符:

__桥

__bridge_retained (与CFBridgingRetain函数合作)

__bridge_transfer (与CFBridgingRelease函数合作)

所以我们将从__bridge 开始。它是什么?__bridge 只是另一种说法:“嘿编译器,给我我该死的铸造对象!”。编译器将很乐意这样做并返回给您一个您喜欢的铸造对象!

然而,因为你想要一个这样的“自由”转换对象,仍然有责任为最初分配的对象释放内存。在这种情况下,如果我要这样做:

    NSArray* myArray = [NSArray alloc]init];
    CFArrayRef arrayRef = (__bridge CFArrayRef) myArray;

我仍然负责释放myArray 内存,因为它是最初分配的对象。请记住,__bridge 只是告诉编译器执行转换!而且因为我是在 ARC 下编译的,所以我不必在 myArray 对象上显式调用 [-release],ARC 会为我做这件事!

请注意,__bridge 修饰符可以双向工作!(因此,免费桥接),您可以以同样的方式轻松地将 CF 对象转换为 NS 对象(即免费桥接!),如下所示:

CFArrayRef arrayRef; // allocate this arrayRef and give it a value later on
//... amazing code.....
NSArray* myArray = (__bridge NSArray*)arrayRef;

但由于 CF 对象将是最初分配的对象,我必须调用 CFRelease(whatever);

现在让我们继续讨论__bridge_retained及其在犯罪CFBridgingRetain()中的合作伙伴。这个 __bridge 修饰符明确用于将 NS 对象的所有权转移 到 CF 对象(因此,由于它是 CF 类型的对象,因此我们期望手动 CFRelease(whatever) this)

意思是,如果我要再次执行我的旧场景,但这次使用 __bridge_retained:

 NSArray* myArray = [NSArray alloc]initWithObjects:....]; //insert objects

 CFArrayRef arrayRef = (__bridge_retained) myArray;

arrayRef 对象现在拥有以前由 myArray 指针拥有的内存的显式所有权。因为现在 CF 类型拥有所有权,所以我必须自己使用 CFRelease(whatever); 释放它;

那么CFBridgingRetain()函数在所有这些混乱中扮演什么角色呢?它和我们刚才谈到的演员扮演着同样的角色!让我们看一下 CFBridgingRetain 的函数原型:

    CFTypeRef CFBridgingRetain(id x);

我们可以看到,它几乎只是将整个 (__bridge_retained) 概念简化为一个函数!在“输入”一个 NS 类型的对象后,我们将返回一个 CF 对象!激进的!是的,我知道这太棒了!太酷了,不能一口气坐下来!是的,它还执行内存“所有权”转移……太棒了!

最后但并非最不重要的是__bridge_transfer和全能的CFBridgingRelease()

__bridge_transfer 的工作方式几乎与 __bridge_retained 相反。__bridge_transfer 修饰符将 CF 对象类型的所有权转移到 NS 对象类型。

因此,让我们参考在整个过程中使用的示例来剖析它:

 NSArray* myArray = [NSArray alloc]initWithObjects:....]; //insert objects

 CFArrayRef arrayRef = (__bridge_retained) myArray;

 // at this point, arrayRef holds the ownership
 // Let's add this new line to change things up a bit:

 NSArray* otherArray = (__bridge_transfer NSArray*)arrayRef;

那么我们刚刚编写的这个很棒的小程序到底是做什么的呢?

第 1 步:我们分配了一个 NSArray

第 2 步:我们将数组的所有权传递给 arrayRef 对象

// 在我们继续第3步之前,我们先了解一下此时arrayRef是owner

第 3 步:我们将曾经由 arrayRef 拥有的所有权重新转移回 NSArray*

因为此时 otherArray 指针是所有者,在这一点上,当我们完成时说 [otherArray release]似乎很自然,对吧?好吧,这就是 ARC 发挥作用的地方,它将负责为您释放该数组!

你知道它会变凉吗?这个 __bridge 修饰符的犯罪伙伴:CFBridgingRelease()

让它更酷!CFBridgingRelease 有这个函数原型:

   id CFBridgingRelease(CFTypeRef x);

我们看到,这与使用 __bridge_transfer 进行转换时发生的事情完全相同。而且这个函数还将所有权转移给了 NS 对象!这太棒了!

起初使用 CFBridgingXXX 函数可能更有意义,因为许多 Objective-c 程序员仍然有 NARC 规则的概念:

所有被调用的东西:

一个lloc

保留_

复制_

必须有一个平衡释放调用

所以这样做:

     NSArray* myArray = [[NSArray alloc]init]; 
                                       // there's the A of NARC! 
                                       //(cleaned by ARC)
     CFArrayRef arrayRef = CFBridgingRetain(myArray); // there's the R of NARC!!
     //NSArray* other = CFBridgingRelease(arrayRef); // cleaned up by ARC

由于保留发布相匹配,可以使学习 __bridge 强制转换的过程更容易

如果这一切仍然令人困惑,请这样想:您是指向任何 NS 对象类型的指针。为了保持一致性,假设你是一个 NSArray 指针,它现在没有指向任何东西。所以你可以想象你,作为零指针,站在浴室里关灯。(灯熄灭表示您没有指向任何东西)。

然后,稍后在代码中,您的程序员决定将您分配给一个新的 NSArray。即,他/她这样说:

      you = [[NSArray alloc]init];

突然,你站着的浴室里的灯亮了!你指向一个对象!现在在正常的程序执行中,当您使用完对象时,您释放它。所以在这种情况下,当你用完浴室时,你关掉了灯。

但是不幸的是,您所在的程序不是很“正常”。程序员决定使用一些 CoreFoundation 对象!呸!

他写了这行代码:

    CFArrayRef other = (__bridge_retained CFArrayRef) you;

所以现在发生的事情是,在你离开的同时,另一个人走进了浴室。出于礼貌,你不要关灯,因为有另一个人在使用洗手间,并负责在他/她离开时关灯

在这种情况下,因为厕所的新主人是一个 CF 对象,程序员必须手动释放它。

但是如果他/她要写这个怎么办:

   CFArrayRef ref = (__bridge CFArrayRef) you;

这里发生的事情是,另一个人甚至没有问就闯入了与你同一个洗手间!真没礼貌!最重要的是,他希望你也能在他之后清理干净!所以你,作为一个绅士/女士,当你们俩都完成后,请关掉灯 。

然而,由于你是一个 NS 类型的对象,ARC 来为你清理它:)

最后,如果程序员这样写:

    you = (__bridge_transfer NSArray*)arrayRef;

这里发生的情况与第一种情况完全相反。与其在有人进入的同时离开洗手间,不如在另一个人离开的同时进入洗手间

相同的内存管理规则适用。由于您接管了“拥有”洗手间,因此您必须手动关闭灯。而且因为你是一个 NS 类型的对象,ARC 会为你做这件事......再次:) ARC 不是这么美吗!

我知道这可能看起来有点令人生畏和困惑,但只要按照自己的方式进行操作,再读一遍,你就会发现这个 ARC 机制的工作原理是多么不可思议!

感谢大家阅读!希望这有帮助:)

感谢@rob mayoff 和@ Krishnabhadra 提供的所有额外帮助和建议!

于 2013-01-08T03:53:35.600 回答