处理 ARC 中的指针对指针所有权问题 [英] Handling Pointer-to-Pointer Ownership Issues in ARC

查看:30
本文介绍了处理 ARC 中的指针对指针所有权问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设对象A有一个属性:

@property (nonatomic, strong) Foo * bar;

在实现中综合为:

@synthesize bar = _bar;

Object B 操作一个 Foo **,就像在这个例子中来自 Object A 的调用一样:

Foo * temp = self.bar;[objB doSomething:&temp];self.bar = temp;

  • 这或类似的事情可以合法地完成吗?
  • doSomething: 方法的正确声明是什么?

此外,假设 Object B 可能在我有机会设置 bar 属性之前被释放(从而获得 指向的实例的所有权temp) - 我如何告诉 ARC 移交拥有的参考?换句话说,如果我想让下面的示例代码段起作用,我需要如何处理 ARC 问题?

Foo * temp = self.bar;//给它一个当前值的引用[objB doSomething:&temp];//让它修改引用self.bar = nil;//基本上释放我们拥有的任何东西_bar = 温度;//因为我们要取回一个拥有的引用,所以绕过 setter

  • 我在想什么?

编辑

基于@KevinBallard 的回答,我只是想确认一下我的理解.这是正确的吗?

对象 A:

@implementation ObjectA@synthesize bar = _bar;- (void)someMethod{ObjectB * objB = [[ObjectB alloc] initWithFoo:&_bar];//objB 传递到某个地方,最终它的doSomething"方法被调用.}@结尾

对象 B:

@implementation ObjectB{Foo * __autoreleasing * _temp;}- (id)initWithFoo:(Foo * __autoreleasing *)temp{id self = [超级初始化];如果(自己){_temp = 温度;}回归自我;}- (void)doSomething{...*_temp = [[Foo alloc] init];...}@结尾

这会产生编译时错误:将非本地对象的地址传递给 __autoreleasing 参数以进行回写

解决方案

ARC 需要知道对象引用的所有权,以便确定何时释放它等.对于任何变量(局部、实例或全局)ARC 都有规则用于确定所有权;通过推理或通过显式属性.这相当于程序员在 ARC 之前需要跟踪所有权.

但是如果你有一个变量的引用会发生什么?您不能(ARC 之前)自己编写接受对变量的引用的代码,并且无论该变量的所有权如何,该代码始终可以正常工作-因为您不知道是否需要发布等.即您无法构建适用于变量(在更改的意义上!)未知所有权的代码.

ARC 面临同样的问题,其解决方案是推断或接受显式属性指定引用变量的所有权,然后要求 调用者 安排对适当所有权的变量的引用要通过.后一位可能需要使用隐藏的临时变量.这被称为最少坏的解决方案".在规范中是称为pass-by-writeback".

问题的第一部分:

<块引用>

Foo * temp = self.bar;[objB doSomething:&temp];self.bar = temp;

  • 这或类似的事情可以合法地完成吗?

是的,ARC 的代码很好.temp 被推断为 strong 并且一些幕后的东西碰巧通过引用 doSomething: 来传递它.

<块引用>
  • doSomething: 方法的正确声明是什么?

- (void) doSomething:(Foo **)byRefFoo

ARC 推断 byRefFoo 的类型为 Foo * __autoreleasing * - 对自动释放引用的引用.这是pass-by-writeback"所要求的.

此代码有效,因为 temp 是本地代码.使用实例变量执行此操作是不正确的(正如您在编辑中发现的那样).它也有效,假设参数在标准输出"中使用.当 doSomething: 返回时,模式和任何更新的值都已被分配.这两者都是因为传递写回的方式是最不坏的解决方案"的一部分......

总结:当使用局部变量时,它们可以通过引用传递以在标准的out"中使用.使用 ARC 推断任何必需属性等的模式

引擎盖下

我们将使用类型 Breadcrumbs 代替问题的 Foo;这本质上是一个包裹的 NSString,它跟踪每个 initretainreleaseautorelease> 和 dealloc(几乎就像你在下面看到的那样)所以我们可以看到发生了什么.Breadcrumbs 是如何编写的并不重要.

现在考虑以下类:

@implementation ByRef{面包屑 * 实例;//__strong 推断}

改变引用传递的值的方法:

- (void) 间接:(Breadcrumbs **)byRef//__autoreleasing inferred{*byRef = [Breadcrumbs newWith:@banana"];}

indirect: 的一个简单包装器,因此我们可以看到它传递了什么以及何时返回:

- (void)indirectWrapper:(Breadcrumbs **)byRef//__autoreleasing inferred{NSLog(@"间接: 传递引用 %p, 包含 %p - %@, 所有者 %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);[自我间接:byRef];NSLog(@间接:返回");}

还有一个方法来演示indirect:调用局部变量(想象中的local):

-(无效)demo1{NSLog(@Strong local 通过自动释放引用传递");面包屑 *local;//__strong 推断local = [Breadcrumbs newWith:@apple"];NSLog(@"local: addr %p, contains %p - %@, owner %lu", &local, local, local, [local ownerCount]);[自我间接包装器:&local];NSLog(@"local: addr %p, contains %p - %@, owner %lu", &local, local, local, [local ownerCount]);}@结尾

现在一些代码来练习 demo1 本地化自动释放池,这样我们就可以看到什么被分配,什么时候被释放:

ByRef *test = [ByRef new];NSLog(@开始演示1");@autoreleasepool{[测试演示1];NSLog(@Flush demo1");}NSLog(@"End demo1");

在控制台上执行上述操作会产生以下结果:

ark[2041:707] 开始演示1ark[2041:707] 通过自动释放引用传递的强本地方舟[2041:707] >>>0x100176f30:初始化ark[2041:707] 本地:地址 0x7fff5fbfedc0,包含 0x100176f30 - 苹果,所有者 1ark[2041:707] 间接:传递引用 0x7fff5fbfedb8,包含 0x100176f30 - 苹果,所有者 1方舟[2041:707] >>>0x100427d10:初始化方舟[2041:707] >>>0x100427d10:自动释放方舟[2041:707] 间接:返回方舟[2041:707] >>>0x100427d10:保留方舟[2041:707] >>>0x100176f30:释放方舟[2041:707] >>>0x100176f30:解除分配ark[2041:707] 本地:地址 0x7fff5fbfedc0,包含 0x100427d10 - 香蕉,所有者 2方舟[2041:707] >>>0x100427d10:释放方舟[2041:707]冲洗演示1方舟[2041:707] >>>0x100427d10:释放方舟[2041:707] >>>0x100427d10:解除分配方舟[2041:707]结束演示1

[>>>"行来自 Breadcrumbs.] 只需按照对象 (0x100...) 和变量 (0x7fff...) 的地址就可以了...

也许不是!这里又是每个块之后的注释:

ark[2041:707] 开始演示1ark[2041:707] 通过自动释放引用传递的强本地方舟[2041:707] >>>0x100176f30:初始化ark[2041:707] 本地:地址 0x7fff5fbfedc0,包含 0x100176f30 - 苹果,所有者 1

在这里我们看到 [Breadcrumbs newWith:@"apple"] 在地址 0x100176f30 创建了一个对象.这个存放在local中,地址为0x7fff5fbfedc0,对象有1个owner(local).

ark[2041:707] 间接:传递引用 0x7fff5fbfedb8,包含 0x100176f30 - 苹果,所有者 1

隐藏变量来了:因为indirect:需要一个自动释放变量的引用ARC创建了一个新变量,地址为0x7fff5fbfedb8,并复制了对象引用(0x100176f30) 到那个.

ark[2041:707] >>>0x100427d10:初始化方舟[2041:707] >>>0x100427d10:自动释放方舟[2041:707] 间接:返回

Inside indirect: 创建一个新对象,ARC 在分配之前自动释放它 - 因为传递的引用引用一个自动释放变量.

<块引用>

注意: ARC 不需要对引用变量 (0x7fff5fbfedb8<) 的上一个内容(0x100176f30)做任何事情/code>) 因为它是自动发布,因此不是它的责任.IE.什么是自动释放所有权"意味着任何分配的引用必须已经有效自动释放.当创建隐藏变量时,您会看到 ARC 实际上并没有保留和自动释放其内容 - 它不需要这样做,因为它知道有一个对它的对象的强引用(在 local 中)正在管理.[在下面的最后一个例子中,ARC 确实必须管理这个分配,但它仍然设法避免使用自动释放池.]

ark[2041:707] >>>0x100427d10:保留方舟[2041:707] >>>0x100176f30:释放方舟[2041:707] >>>0x100176f30:解除分配

这些操作源于将隐藏变量中的值复制(回写调用中的回写")到 local.release/dealloc 用于local 中的旧强引用,retain 用于隐藏变量引用的对象(由indirect: 自动释放)

<块引用>

注意:这个写回是为什么这只适用于out"使用传递引用的模式 - 你不能存储传递给 indirect: 的引用,因为它是一个隐藏的局部变量,即将消失......

ark[2041:707] 本地:地址 0x7fff5fbfedc0,包含 0x100427d10 - 香蕉,所有者 2

所以调用后local指的是新对象,它有2个owner——local占一个,另一个是indirect:

中的autorelease

ark[2041:707] >>>0x100427d10:释放

demo1 现已完成,因此 ARC 释放 local

中的对象

ark[2041:707] Flush demo1方舟[2041:707] >>>0x100427d10:释放方舟[2041:707] >>>0x100427d10:解除分配方舟[2041:707]结束演示1

并且在 demo1 返回本地化的 @autoreleasepool 之后处理来自 indirect: 的自动释放挂起,现在所有权为零,我们得到 <代码>dealloc.

通过引用传递实例变量

以上处理通过引用传递局部变量,但不幸的是,通过写回传递不适用于实例变量.有两种基本的解决方案:

  • 将您的实例变量复制到本地

  • 添加一些属性

为了演示第二个,我们在 ByRef 类中添加了一个 strongIndirect:,它指定它需要一个强变量的引用:

- (void) strongIndirect:(Breadcrumbs * __strong *)byRef{*byRef = [Breadcrumbs newWith:@plum"];}- (void) strongIndirectWrapper:(Breadcrumbs * __strong *)byRef{NSLog(@"strongIndirect: 传递引用 %p, 包含 %p - %@, 所有者 %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);[self strongIndirect:byRef];NSLog(@"strongIndirect: 返回");}

和相应的 demo2,它使用 ByRef 的实例变量(同样使用富有想象力的 instance 名称):

-(无效)demo2{NSLog(@强引用传递的强实例");instance = [Breadcrumbs newWith:@orange"];NSLog(@"instance: addr %p, contains %p - %@, owner %lu", &instance, instance, instance, [instance ownerCount]);[self strongIndirectWrapper:&instance];NSLog(@"instance: addr %p, contains %p - %@, owner %lu", &instance, instance, instance, [instance ownerCount]);}

使用与上面的 demo1 类似的一段代码执行此操作,我们得到:

1 ark[2041:707] 开始demo22 ark[2041:707] 通过强引用传递的强实例3方舟[2041:707]0x100176f30:初始化4 ark[2041:707] 实例:地址 0x100147518,包含 0x100176f30 - 橙色,所有者 15 ark[2041:707] strongIndirect:传递引用 0x100147518,包含 0x100176f30 - 橙色,所有者 16方舟[2041:707]0x100427d10:初始化7方舟[2041:707]0x100176f30:释放8方舟[2041:707]0x100176f30:解除分配9 ark[2041:707] strongIndirect:返回10 ark[2041:707] 实例:地址 0x100147518,包含 0x100427d10 - 李子,所有者 111方舟[2041:707]同花顺demo212方舟[2041:707]结束demo2

这比以前短了一点.这有两个原因:

  • 因为我们将一个强变量 (instance) 传递给一个方法 (strongIndirect:),该方法需要一个强变量的引用,所以不需要ARC 使用隐藏变量 - 上面第 4 行和第 5 行中的变量相同(0x100147518).

  • 由于 ARC 知道 strongIndirect: 中的引用变量很强,因此无需在 strongIndirect: 中存储自动释放的引用,然后在call - ARC 只是做了一个标准的强赋值,第 6-8 行,之后没有什么可以自动释放的(在第 11 和 12 行之间).

strongIndirect:是否适用于强大的本地人?

当然,这里是demo3:

-(无效)demo3{NSLog(@通过强引用传递强本地");面包屑 *local;//__strong 推断local = [Breadcrumbs newWith:@apple"];NSLog(@"local: addr %p, contains %p - %@, owner %lu", &local, local, local, [local ownerCount]);[self strongIndirectWrapper:&local];NSLog(@"local: addr %p, contains %p - %@, owner %lu", &local, local, local, [local ownerCount]);}

使用我们的标准包装器执行此操作会产生:

1 ark[2041:707] 开始demo32 ark[2041:707] 强本地通过强引用传递3方舟[2041:707]0x100176f30:初始化4 ark[2041:707] 本地:地址 0x7fff5fbfedc0,包含 0x100176f30 - 苹果,所有者 15 ark[2041:707] strongIndirect:传递引用 0x7fff5fbfedc0,包含 0x100176f30 - 苹果,所有者 16方舟[2041:707]0x100427d20:初始化7方舟[2041:707]0x100176f30:释放8方舟[2041:707]0x100176f30:解除分配9 ark[2041:707] strongIndirect:返回10 ark[2041:707] 本地:地址 0x7fff5fbfedc0,包含 0x100427d20 - 李子,所有者 111方舟[2041:707] >>>0x100427d20:释放12方舟[2041:707]0x100427d20:解除分配13方舟[2041:707]同花顺demo314方舟[2041:707]结束demo3

这和前面的例子几乎一样,只有两个细微的区别:

  • 传递栈上本地的地址(0x7fff5fbfedc0),第4行和第5行

  • 当它存储在本地时,新对象被 ARC 清理,第 11 和 12 行

为什么不总是将 __strong 添加到引用参数中?

一个原因是因为并非一切都是强大的!ARC 的 pass-by-writeback 也适用于弱本地人.我们的最终演示:

-(无效)demo4{NSLog(@弱本地通过自动释放引用传递");instance = [Breadcrumbs newWith:@桃子"];面包屑 __weak *weakLocal = 实例;NSLog(@"weakLocal: addr %p, contains %p - %@, owner %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]);[自我间接包装器:&weakLocal];NSLog(@"weakLocal: addr %p, contains %p -, %@, owner %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]);}

[这里我们刚刚使用了 instance,所以我们有一些东西可以弱引用.]

使用我们的标准包装器执行此操作会产生:

 1 ark[2041:707] 开始demo42 ark[2041:707] 弱局部通过自动释放引用传递3方舟[2041:707]0x608000000d10:初始化4 ark[2041:707]weakLocal:addr 0x7ffeefbfde58,包含0x608000000d10 - 桃子,所有者45方舟[2041:707] >>>0x608000000d10:retainWeakReference6 ark[2041:707] 间接:传递引用 0x7ffeefbfde40,包含 0x608000000d10 - 桃子,所有者 27方舟[2041:707]0x604000001060:初始化8方舟[2041:707]0x604000001060:自动释放9 ark[2041:707] 间接:返回10方舟[2041:707]0x608000000d10:释放11 ark[2041:707] weakLocal:地址 0x7ffeefbfde58,包含 0x604000001060 - 香蕉,所有者 412方舟[2041:707]冲洗演示413方舟[2041:707]0x604000001060:释放14方舟[2041:707]0x604000001060:解除分配15方舟[2041:707]结束demo416方舟[2041:707]0x608000000d10:释放17方舟[2041:707]0x608000000d10:解除分配

注意事项:

  • 第 3、16 行和17 与instance相关——创建新值并发布&最后释放 - 重要的东西从第 4 行开始

  • 第 4 行显示了已分配给 weakLocal 的内容,请注意,将强引用从 `instance 复制到这个弱变量中不需要任何保留.(注意:显示弱变量的内容确实涉及一些保留和释放操作,为了清楚起见,这些操作已被省略.)

  • ARC 也为弱局部变量(第 4 行,0x7ffeefbfde58)使用了一个隐藏变量(第 6 行,0x7ffeefbfde40).在强本地情况下 (demo1) ARC 知道存储在这个隐藏变量中的引用将保持有效并避免使用自动释放池.在这种情况下,这不能保证,但 ARC 仍然设法避免自动释放池:ARC 保留引用(第 5 行,retainWeakReference 是用于弱变量的 retain 的特殊版本) 并在调用完成后将其与发布进行平衡(第 10 行).与使用自动释放池相比,这缩短了强引用的生命周期.

  • indirectWrapper(第 8 行)内的自动释放分配(0x604000001060)在池被排空时(第 13 和 14 行)平衡.最后,当我们的 ByRef 实例消失时,ARC 会清理存储在 instance (0x608000000d10) 中的对象.

总结

  • 在没有任何添加属性的情况下,ARC 将对作为参数传递的本地(推断强)变量通过引用(推断自动释放)做正确的事情.(并且local"包括当前方法的参数.)

  • 这是由 ARC 使用 pass-by-writeback 实现的,并且在您遵循out"的情况下才有效.参数模式.如果您希望存储传递的引用供以后使用,您需要自己做更多的事情.

  • 如果您希望通过引用传递实例变量,您需要将它们复制到本地变量中或使用 __strong 属性接收参数类型.

  • pass-by-writeback 也适用于 __weak 本地人.

希望有所帮助.


2016 年 4 月附录:__block 变量

在 Heath Borders 提出的评论中:

<块引用>

如果我的局部变量是 __block 类型怎么办?我很确定这种情况与实例变量相同,因为我需要将它们复制到本地,或者使用 __strong 属性接收参数类型,但我很好奇其他人的意见.

有趣的问题.

规范状态:

<块引用>

如果参数表达式没有合法形式,则传递回写是格式错误的:

&var,其中var是具有可保留对象指针类型的自动存储期的标量变量

(Objective-)C 中的局部变量默认具有自动存储持续时间 - 它们在进入/退出封闭函数/方法/块时自动创建和销毁.在上面的答案中,当我们提到局部变量"时我们隐式地引用了具有自动存储持续时间的局部变量.

局部变量可以用storage qualifierstorage class specifier 来声明,以改变变量的存储持续时间.最常见的是static;具有静态存储期的局部变量存在于程序的整个执行过程中,但只能(直接)在其局部范围内访问.

如果您尝试通过传递写回传递 static 局部变量,编译器将产生一个错误,指示该变量没有自动存储持续时间.您必须以与实例变量(具有分配的存储持续时间)相同的方式处理此类变量.

__block 存储限定符 作为块的一部分被引入到 (Objective-)C 中,并且规范指出:

<块引用>

__block 存储限定符与现有的本地存储限定符 autoregisterstatic 互斥.__block 限定的变量就好像它们在分配的存储中一样,并且在上次使用所述变量后会自动恢复该存储.

所以一个 __block 局部变量就好像它已经分配了存储持续时间一样,就像实例变量一样,所以根据 pass-by-writeback 的规范,这样的变量不能被使用,因为它没有有自动存储时长...

但是 使用编写时当前的工具(Xcode 7.2、Clang 7.0.2)__block 限定的局部变量由 pass-by-writeback 支持并且是处理与自动存储持续时间相同的处理 - 使用隐藏的 __autoreleasing 临时.

这似乎没有记录.

说它是安全的";在某种意义上使用它会编译或不编译,并且一旦编译,即使工具发生变化,代码也会工作,并且将来无法再次编译......(至少不处理与实例变量相同的变量)必须处理).

能被接受的原因可以从对 pass-by-writeback 的限制的理由(强调):

<块引用>

基本原理

参数形式的限制有两个目的.首先,它不可能将数组的地址传递给参数,这有助于防止将数组"参数错误推断为输出参数的严重风险.第二,由于下面的实现,用户不太可能看到混淆的别名问题,在原始参数变量中不会立即看到他们对写回临时文件的存储.

通过回写不支持实例变量没有技术原因,但由于别名可能会造成混淆.__block 变量介于自动变量和分配变量之间,因此当前的工具编写者可能会选择将它们与前者而不是后者分组以进行传递回写.

<块引用>

注意:熟悉块实现的读者会知道,__block 限定的本地可以实现为优化,自动或分配存储持续时间,具体取决于使用情况,因此想知道这是否会影响它们用于传递写回.情况似乎并非如此.

Suppose Object A has a property:

@property (nonatomic, strong) Foo * bar;

Synthesized in the implementation as:

@synthesize bar = _bar;

Object B manipulates a Foo **, as in this example call from Object A:

Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;

  • Can this, or something similar, be done legitimately?
  • What is the correct declaration for the doSomething: method?

Furthermore, suppose Object B may be deallocated before I have a chance to set the bar property (and thus take on ownership of the instance pointed to by temp) - How would I tell ARC to hand off an owning reference? In other words, if I wanted the following example snippet to work, how would I need to handle the ARC issues?

Foo * temp = self.bar;    // Give it a reference to some current value
[objB doSomething:&temp]; // Let it modify the reference
self.bar = nil;           // Basically release whatever we have
_bar = temp;              // Since we're getting back an owning reference, bypass setter

  • What aren't I thinking of?

EDIT

Based on @KevinBallard 's answer, I just want to confirm my understanding. Is this correct?

Object A:

@implementation ObjectA

@synthesize bar = _bar;

- (void)someMethod
{
    ObjectB * objB = [[ObjectB alloc] initWithFoo:&_bar];
    // objB handed off somewhere and eventually it's "doSomething" method is called.
}

@end

Object B:

@implementation ObjectB
{
    Foo * __autoreleasing * _temp;
}

- (id)initWithFoo:(Foo * __autoreleasing *)temp
{
    id self = [super init];
    if (self)
    {
        _temp = temp;
    }
    return self;
}

- (void)doSomething
{
    ...
    *_temp = [[Foo alloc] init]; 
    ...
}

@end

This creates a compile-time error: passing address of non-local object to __autoreleasing parameter for write-back

解决方案

ARC needs to know the ownership of an object reference so it can determine when to release it etc. For any variable (local, instance or global) ARC has rules for determining the ownership; either by inference or by an explicit attribute. This equates to the pre-ARC need for the programmer to track ownership.

But what happens if you have a reference to a variable? You could not (pre-ARC) yourself write code which accepted a reference to a variable and which would always work correctly regardless of the ownership of that variable - as you could not know whether you needed to release etc. I.e. you can not construct code which works for variable (in the sense of changing!) unknown ownership.

ARC faces the same problem and its solution is to infer, or accept an explicit attribute specifying, the ownership of referenced variable and then require the caller to arrange for a reference to a variable of appropriate ownership to be passed. This latter bit can require the use of hidden temporary variables. This is referred to as "least bad solution" in the specification and is termed "pass-by-writeback".

The first part of the question:

Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;

  • Can this, or something similar, be done legitimately?

Yes, the code is fine by ARC. temp is inferred to be strong and some behind the scenes stuff happens to pass it by reference to doSomething:.

  • What is the correct declaration for the doSomething: method?

- (void) doSomething:(Foo **)byRefFoo

ARC infers byRefFoo to be of type Foo * __autoreleasing * - a reference to an autoreleasing reference. This is what is required by "pass-by-writeback".

This code is only valid because temp is a local. It would be incorrect to do this with an instance variable (as you found out in your EDIT). It is also only valid assuming the parameter is being used in standard "out" mode and any updated value has been assign when doSomething: returns. Both of these are because the way pass-by-writeback works as part of that "least bad solution"...

Summary: when using local variables they can be passed by reference for use in the standard "out" pattern with ARC inferring any required attributes etc.

Under The Hood

Instead of the Foo of the question we'll use a type Breadcrumbs; this is essentially a wrapped NSString which tracks every init, retain, release, autorelease and dealloc (well almost as you'll see below) so we can see what is going on. How Breadcrumbs is written is not material.

Now consider the following class:

@implementation ByRef
{
   Breadcrumbs *instance;                                // __strong inferred
}

A method to change a value passed by reference:

- (void) indirect:(Breadcrumbs **)byRef                  // __autoreleasing inferred
{
   *byRef = [Breadcrumbs newWith:@"banana"];
}

A simple wrapper for indirect: so we can see what it is passed and when it returns:

- (void) indirectWrapper:(Breadcrumbs **)byRef           // __autoreleasing inferred
{
   NSLog(@"indirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);
   [self indirect:byRef];
   NSLog(@"indirect: returned");
}

And a method to demonstrate indirect: called on a local variable (called imaginatively local):

- (void) demo1
{
   NSLog(@"Strong local passed by autoreleasing reference");
   Breadcrumbs *local;                                   // __strong inferred
   local = [Breadcrumbs newWith:@"apple"];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
   [self indirectWrapper:&local];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
}

@end

Now some code to exercise demo1 localizing the autorelease pool so we can see what is allocated, released and when:

ByRef *test = [ByRef new];

NSLog(@"Start demo1");
@autoreleasepool
{
   [test demo1];
   NSLog(@"Flush demo1");
}
NSLog(@"End demo1");

Executing the above produces the following on the console:

ark[2041:707] Start demo1
ark[2041:707] Strong local passed by autoreleasing reference
ark[2041:707] >>> 0x100176f30: init
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1
ark[2041:707] >>> 0x100427d10: init
ark[2041:707] >>> 0x100427d10: autorelease
ark[2041:707] indirect: returned
ark[2041:707] >>> 0x100427d10: retain
ark[2041:707] >>> 0x100176f30: release
ark[2041:707] >>> 0x100176f30: dealloc
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] Flush demo1
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] >>> 0x100427d10: dealloc
ark[2041:707] End demo1

[The ">>>" lines come from Breadcrumbs.] Just follow the addresses of the objects (0x100...) and variables (0x7fff...) and it is all clear...

Well maybe not! Here it is again with comments after each chunk:

ark[2041:707] Start demo1
ark[2041:707] Strong local passed by autoreleasing reference
ark[2041:707] >>> 0x100176f30: init
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1

Here we see that [Breadcrumbs newWith:@"apple"] creates an object at address 0x100176f30. This is stored in local, whose address is 0x7fff5fbfedc0, and the object has 1 owner (local).

ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1

Here comes the hidden variable: as indirect: requires a reference to an autoreleasing variable ARC has created a new variable, whose address is 0x7fff5fbfedb8, and copied the object reference (0x100176f30) into that.

ark[2041:707] >>> 0x100427d10: init
ark[2041:707] >>> 0x100427d10: autorelease
ark[2041:707] indirect: returned

Inside indirect: a new object is created and ARC autoreleases it before assigning it - because the passed references refers to an autoreleasing variable.

Note: ARC does not need to do anything with the previous contents (0x100176f30) of the referenced variable (0x7fff5fbfedb8) as it is autoreleasing and hence not its responsibility. I.e. what "autoreleasing ownership" means is that any reference assigned must have already been effectively autoreleased. You'll see when creating the hidden variable ARC did not actually retain and autorelease its contents - it did not need to do this as it knows there is a strong reference (in local) to the object which it is managing. [In the last example below ARC does have to manage this assignment but it still manages to avoid using the autorelease pool.]

ark[2041:707] >>> 0x100427d10: retain
ark[2041:707] >>> 0x100176f30: release
ark[2041:707] >>> 0x100176f30: dealloc

These actions result from copying (the "writeback" in call-by-writeback) the value from the hidden variable into local. The release/dealloc are for the old strong reference in local, and the retain is for the object referenced by the hidden variable (which was autoreleased by indirect:)

Note: this writeback is why this only works for the "out" pattern of using pass-by-reference - you can't store the reference passed to indirect: as it is to a hidden local variable which is about to disappear...

ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2

So after the call local refers to the new object, and it has 2 owners - local accounts for one, and the other is the autorelease in indirect:

ark[2041:707] >>> 0x100427d10: release

demo1 is now finished so ARC releases the object in local

ark[2041:707] Flush demo1
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] >>> 0x100427d10: dealloc
ark[2041:707] End demo1

and after demo1 returns the localized @autoreleasepool handles the autorelease pending from indirect:, now the ownership is zero and we get the dealloc.

Passing instance variables by reference

The above deals with passing local variables by reference, but unfortunately pass-by-writeback does not work for instance variables. There are two basic solutions:

  • copy your instance variable to a local

  • add some attributes

To demonstrate the second we add to class ByRef a strongIndirect: which specifies it requires a reference to a strong variable:

- (void) strongIndirect:(Breadcrumbs * __strong *)byRef
{
   *byRef = [Breadcrumbs newWith:@"plum"];
}

- (void) strongIndirectWrapper:(Breadcrumbs * __strong *)byRef
{
   NSLog(@"strongIndirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);
   [self strongIndirect:byRef];
   NSLog(@"strongIndirect: returned");
}

and a corresponding demo2 which uses ByRef's instance variable (again with the imaginative name of instance):

- (void) demo2
{
   NSLog(@"Strong instance passed by strong reference");
   instance = [Breadcrumbs newWith:@"orange"];
   NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]);
   [self strongIndirectWrapper:&instance];
   NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]);
}

Execute this with a similiar piece of code as for demo1 above and we get:

1  ark[2041:707] Start demo2
2  ark[2041:707] Strong instance passed by strong reference
3  ark[2041:707] >>> 0x100176f30: init
4  ark[2041:707] instance: addr 0x100147518, contains 0x100176f30 - orange, owners 1
5  ark[2041:707] strongIndirect: passed reference 0x100147518, contains 0x100176f30 - orange, owners 1
6  ark[2041:707] >>> 0x100427d10: init
7  ark[2041:707] >>> 0x100176f30: release
8  ark[2041:707] >>> 0x100176f30: dealloc
9  ark[2041:707] strongIndirect: returned
10 ark[2041:707] instance: addr 0x100147518, contains 0x100427d10 - plum, owners 1
11 ark[2041:707] Flush demo2
12 ark[2041:707] End demo2

Which is a bit shorter than before. This is for two reasons:

  • As we are passing a strong variable (instance) to a method (strongIndirect:) which expects a reference to a strong variable there is no need for ARC to use a hidden variable - the variables in line 4 and 5 above are the same (0x100147518).

  • As ARC knows the referenced variable in strongIndirect: is strong there is no need to store an autoreleased reference within strongIndirect: and then write this back after the call - ARC just does a standard strong assignment, lines 6-8, and there is nothing to autorelease later (between lines 11 and 12).

Does strongIndirect: work for strong locals?

Of course, here is demo3:

- (void) demo3
{
   NSLog(@"Strong local passed by strong reference");
   Breadcrumbs *local;                                   // __strong inferred
   local = [Breadcrumbs newWith:@"apple"];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
   [self strongIndirectWrapper:&local];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
}

Executing this with our standard wrapper produces:

1  ark[2041:707] Start demo3
2  ark[2041:707] Strong local passed by strong reference
3  ark[2041:707] >>> 0x100176f30: init
4  ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
5  ark[2041:707] strongIndirect: passed reference 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
6  ark[2041:707] >>> 0x100427d20: init
7  ark[2041:707] >>> 0x100176f30: release
8  ark[2041:707] >>> 0x100176f30: dealloc
9  ark[2041:707] strongIndirect: returned
10 ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d20 - plum, owners 1
11 ark[2041:707] >>> 0x100427d20: release
12 ark[2041:707] >>> 0x100427d20: dealloc
13 ark[2041:707] Flush demo3
14 ark[2041:707] End demo3

This is almost the same as the previous example, just two minor differences:

  • The address of the local on the stack is passed (0x7fff5fbfedc0), lines 4 and 5

  • As it is stored in a local the new object is cleaned up by ARC, lines 11 and 12

Why not always add __strong to reference arguments?

One reason is because not everything is strong! ARC's pass-by-writeback works for weak locals as well. Our final demo:

- (void) demo4
{
   NSLog(@"Weak local passed by autoreleasing reference");
   instance = [Breadcrumbs newWith:@"peach"];
   Breadcrumbs __weak *weakLocal = instance;
   NSLog(@"weakLocal: addr %p, contains %p - %@, owners %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]);
   [self indirectWrapper:&weakLocal];
   NSLog(@"weakLocal: addr %p, contains %p -, %@, owners %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]);
}

[Here we've just used instance so we have something to make a weak reference to.]

Executing this with our standard wrapper produces:

 1 ark[2041:707] Start demo4
 2 ark[2041:707] Weak local passed by autoreleasing reference
 3 ark[2041:707] >>> 0x608000000d10: init
 4 ark[2041:707] weakLocal: addr 0x7ffeefbfde58, contains 0x608000000d10 - peach, owners 4
 5 ark[2041:707] >>> 0x608000000d10: retainWeakReference
 6 ark[2041:707] indirect: passed reference 0x7ffeefbfde40, contains 0x608000000d10 - peach, owners 2
 7 ark[2041:707] >>> 0x604000001060: init
 8 ark[2041:707] >>> 0x604000001060: autorelease
 9 ark[2041:707] indirect: returned
10 ark[2041:707] >>> 0x608000000d10: release
11 ark[2041:707] weakLocal: addr 0x7ffeefbfde58, contains 0x604000001060 - banana, owners 4
12 ark[2041:707] Flush demo4
13 ark[2041:707] >>> 0x604000001060: release
14 ark[2041:707] >>> 0x604000001060: dealloc
15 ark[2041:707] End demo4
16 ark[2041:707] >>> 0x608000000d10: release
17 ark[2041:707] >>> 0x608000000d10: dealloc

Notes:

  • Lines 3, 16 & 17 are related to instance - create a new value and release & deallocated at the end - the important stuff starts at line 4

  • Line 4 shows what has been assigned to weakLocal, note that copying the strong reference from `instance into this weak variable does not require any retain. (Note: displaying the contents of a weak variable does involved some retain and release operations, these have been elided for clarity.)

  • ARC uses a hidden variable (line 6, 0x7ffeefbfde40) for weak locals (line 4, 0x7ffeefbfde58) as well. In the strong local case (demo1) ARC knows the reference stored in this hidden variable will remain valid and avoids using the autorelease pool. In this case this isn't guaranteed but ARC still manages to avoid the autorelease pool: ARC retains the reference (line 5, retainWeakReference is a special version of retain for weak variables) and after the call has completed balances this with a release (line 10). This shortens the lifetime of the strong reference compared to using the autorelease pool.

  • The autoreleased assignment (0x604000001060) inside indirectWrapper (line 8) is balanced when the pool is drained (lines 13 & 14). Finally ARC cleans up the object stored in instance (0x608000000d10) when our ByRef instance goes away.

Summary

  • Without any added attributes ARC will do the right thing for local (inferred strong) variables passed as parameters by reference (inferred autoreleasing). (And "local" includes parameters to the current method.)

  • This is implemented by ARC using pass-by-writeback and only works if you follow the "out" parameter pattern. If you wish to store the passed reference for use later you'll need to do more yourself.

  • If you wish to pass instance variables by reference you either need to copy them into locals or attribute the receiving parameter type with __strong.

  • pass-by-writeback also works for __weak locals.

Hope that helps.


Addendum Apr 2016: __block variables

In the comments Heath Borders has asked:

What if my local variable is a __block type? I'm pretty sure this case is the same as an instance variable in that I need to either copy them to locals, or attribute the receiving parameter type with __strong, but I'm curious about someone else's opinion.

Interesting question.

The specification states:

The pass-by-writeback is ill-formed if the argument expression does not have a legal form:

&var, where var is a scalar variable of automatic storage duration with retainable object pointer type

Local variables in (Objective-)C by default have automatic storage duration - they are automatically created and destroyed as their enclosing function/method/block is entered/exited. In the above answer when we refer to "local variable" we are implicitly referring to local variables with automatic storage duration.

Local variables can be declared with a storage qualifier or storage class specifier to change the storage duration of the variable. The most commonly seen one is static; local variables with static storage duration exist throughout the execution of the program but are only (directly) accessible within their local scope.

If you attempt to pass a static local variable with pass-by-writeback the compiler will produce an error indicating the variable does not have automatic storage duration. You must handle such variables in the same way as instance variables (which have allocated storage duration).

The __block storage qualifier was introduced into (Objective-)C as part of blocks and the specification states:

The __block storage qualifier is mutually exclusive to the existing local storage qualifiers auto, register, and static. Variables qualified by __block act as if they were in allocated storage and this storage is automatically recovered after last use of said variable.

So a __block local variable acts as if it has allocated storage duration, just like instance variables, and so by the specification of pass-by-writeback such a variable cannot be used as it does not have automatic storage duration...

However with the tools current at the time of writing (Xcode 7.2, Clang 7.0.2) __block qualified local variables are supported by pass-by-writeback and are handle the same as those with automatic storage duration - a hidden __autoreleasing temporary is used.

This appears to be undocumented.

Having said that it is "safe" to use in the sense that it will either compile or not, and once compiled the code will work even if the tools change and it cannot be compiled again in the future... (at least without handling the variable the same was as instance variables must be handled).

The reason why it can be accepted can be gleaned from the rationale for the restrictions on pass-by-writeback (emphasis added):

Rationale

The restriction in the form of the argument serves two purposes. First, it makes it impossible to pass the address of an array to the argument, which serves to protect against an otherwise serious risk of mis-inferring an "array" argument as an out-parameter. Second, it makes it much less likely that the user will see confusing aliasing problems due to the implementation, below, where their store to the writeback temporary is not immediately seen in the original argument variable.

There is no technical reason why instance variables could not be supported by pass-by-writeback, but it could be confusing due to aliasing. __block variables lie somewhere between automatic and allocated ones, so maybe the current tool writers choose to group them with the former rather than the latter for pass-by-writeback.

Note: Readers familiar with the implementation of blocks will know that a __block qualified local may be implemented as an optimisation with either automatic or allocated storage duration, depending on usage, and therefore wonder whether this impacts their use for pass-by-writeback. This does not appear to be the case.

这篇关于处理 ARC 中的指针对指针所有权问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆