我的一位朋友最近向我询问了在 ARC 下启用的新桥梁修改器。他问我是否知道在特定时间使用哪些,以及不同 __bridge 修饰符之间的区别是什么。他问我,“那么它们是如何工作的,我什么时候使用它们,我如何使用它们,以及它们是如何“在引擎盖下”工作的?
注意:这应该是“分享你的知识”类型的问题,我自己回答了这个问题,但我不确定我是否正确设置了它。
我的一位朋友最近向我询问了在 ARC 下启用的新桥梁修改器。他问我是否知道在特定时间使用哪些,以及不同 __bridge 修饰符之间的区别是什么。他问我,“那么它们是如何工作的,我什么时候使用它们,我如何使用它们,以及它们是如何“在引擎盖下”工作的?
注意:这应该是“分享你的知识”类型的问题,我自己回答了这个问题,但我不确定我是否正确设置了它。
因为我最近才知道它们是什么以及它们是如何运作的,所以我想与其他任何希望了解 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 提供的所有额外帮助和建议!