处理ARC中的指针指针所有权问题

假设对象A有一个属性:

@property (nonatomic, strong) Foo * bar; 

在实现中合成为:

 @synthesize bar = _bar; 

对象B操纵一个Foo ** ,就像这个例子中来自对象A的调用:

 Foo * temp = self.bar; [objB doSomething:&temp]; self.bar = temp; 
  • 这个,或者类似的东西,可以合法地完成吗?
  • 什么是doSomething:方法的正确声明?

此外,假设对象B可能会在我有机会设置bar属性之前解除分配(并因此承担temp指向的实例的所有权),那么我将如何告诉ARC交出拥有的引用? 换句话说,如果我想要下面的示例代码片段工作,我将如何处理ARC问题?

 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 
  • 我不是在想什么?

编辑

基于@KevinBallard的回答,我只想确认我的理解。 它是否正确?

对象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 

对象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 

这会产生编译时错误: passing address of non-local object to __autoreleasing parameter for write-back

ARC需要知道对象引用的所有权,以便确定何时释放对象。对于任何variables(本地,实例或全局),ARC都有确定所有权的规则; 无论是通过推论还是通过明确的属性。 这相当于程序员追踪所有权之前的ARC需求。

但是如果你有一个variables的引用会发生什么? 你不能(预ARC)自己写代码接受了一个variables的引用,哪个总是会正常工作,无论该variables的所有权 – 因为你不知道你是否需要释放等等,即你不能构造代码它适用于variables(在变化的意义上)未知所有权。

ARC面临同样的问题,其解决scheme是推断或接受一个明确的属性,指定被引用variables的所有权,然后要求调用者安排一个适当所有权variables的引用。 后一位可能需要使用隐藏的临时variables。 这在规范中被称为“最不好的解决scheme”,被称为“写回传递”(pass-by-writeback)。

问题的第一部分:

 Foo * temp = self.bar; [objB doSomething:&temp]; self.bar = temp; 
  • 这个,或者类似的东西,可以合法地完成吗?

是的,ARC的代码很好。 我们推断tempstrong ,一些幕后的东西恰好通过参考doSomething:

  • 什么是doSomething:方法的正确声明?
 - (void) doSomething:(Foo **)byRefFoo 

ARC推断byRefFoo的types是Foo * __autoreleasing * – 对自动释放引用的引用。 这是“写回传”所要求的。

这个代码只是有效的,因为temp是一个本地的。 用一个实例variables来做这件事是不正确的(正如你在编辑中发现的那样)。 假设参数在标准“out”模式下使用,并且doSomething:返回时已经分配了任何更新的值,这也是唯一有效的。 这两者都是因为写回传的方式是“最不好的解决scheme”的一部分。

总结 :当使用局部variables时,它们可以通过引用传递,用于标准的“out”模式,用ARC推断任何必需的属性等。

在胡德之下

而不是问题的Foo ,我们将使用typesBreadcrumbs ; 这本质上是一个包装的NSString ,它跟踪每个initretainreleaseautoreleasedealloc (就像你将在下面看到的一样),这样我们就可以看到发生了什么。 Breadcrumbs是如何写的并不重要。

现在考虑下面的类:

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

一种更改通过引用传递的值的方法:

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

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

 - (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"); } 

和一个方法来演示indirect:调用一个局部variables(称为想像力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 

现在一些代码来demo1本地化autorelease池,所以我们可以看到什么是分配,释放和时间:

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

执行以上操作将在控制台上产生以下内容:

 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 

[>>>“行来自Breadcrumbs 。]只要按照对象(0x100 …)和variables(0x7fff …)的地址,这是清楚的…

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

 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 

在这里我们看到[Breadcrumbs newWith:@"apple"]在地址0x100176f30处创build一个对象。 它存储在local ,其地址是0x7fff5fbfedc0 ,对象有1个所有者( local )。

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

隐藏variables:as indirect:需要对自动释放variables的引用ARC创build了一个新variables,其地址为0x7fff5fbfedb8 ,并将对象引用( 0x100176f30 )复制到该0x100176f30中。

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

内部indirect:创build一个新的对象,并在分配之前自动释放它 – 因为传递的引用引用了一个自动释放variables。

注意: ARC不需要对引用variables( 0x7fff5fbfedb8 )的前一个内容( 0x100176f30 )进行任何操作,因为它是自动释放的 ,因此不是它的责任。 即“autoreleasing所有权”的意思是指定的任何参考必须已经有效 autoreleased。 在创build隐藏variables时,您会看到ARC实际上并未保留并自动释放它的内容 – 它不需要这样做,因为它知道它正在pipe理的对象有一个强大的引用(在local )。 [在下面的最后一个例子中ARC不会隐藏retain / autorelease。]

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

这些操作的结果是将隐藏variables的值复制到回写中的“写回”。 release / dealloc是针对local的旧的强引用,而retain则是针对隐藏variables引用的对象(这是通过indirect:自动释放的) indirect:

注意:这个回写就是为什么这只适用于使用传递引用的“out”模式 – 不能将传递给indirect:的引用存储起来,因为它是隐藏的局部variables,它即将消失。

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

所以在local调用之后引用新的对象,它有2个所有者 – local帐户为一个,另一个是indirect:autorelease indirect:

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

demo1现在完成了,所以ARC在local释放对象

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

demo1返回本地化的@autoreleasepool处理从indirect:的autorelease挂起indirect: ,现在所有权为零,我们得到dealloc

通过引用传递实例variables

以上处理通过引用传递局部variables,但不幸的是,写回传不适用于实例variables。 有两个基本的解决scheme:

  • 将你的实例variables复制到本地

  • 添加一些属性

为了演示第二个我们添加到类ByRef一个strongIndirect:它指定它需要一个强参数引用:

 - (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"); } 

和一个相应的demo2 ,它使用ByRef的实例variables(再次以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]); } 

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

 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 

比以前短了一点。 这是有两个原因的:

  • 当我们将一个强大的variables( instance )传递给一个方法( strongIndirect: ,该方法期望引用强variables时,ARC不需要使用隐藏variables – 上面第4行和第5行中的variables是相同的( 0x100147518 )。

  • 由于ARC知道strongIndirect:的引用variablesstrongIndirect:强壮,所以不需要在strongIndirect:存储自动发布的引用strongIndirect:然后在调用之后将其写回 – ARC仅执行标准的强分配,第6-8行,并且没有任何稍后自动释放(在第11行和第12行之间)。

强大的strongIndirect:为强大的本地人服务吗?

当然,这里是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]); } 

用我们的标准包装执行这个产生:

 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 

这和前面的例子几乎是一样的,只是两个小小的区别:

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

  • 由于它存储在本地,所以新的对象由ARC清理,第11行和第12行

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

一个原因是因为不是一切都很好! ARC的写回传递也适用于弱势本地人。 我们最后的演示:

 - (void) demo4 { NSLog(@"Weak instance 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]); } 

[这里我们刚刚使用了instance所以我们有一些东西可以作为一个弱引用。]

用我们的标准包装执行这个产生:

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

笔记:

  • 第3-5行只是设置instance – 创build一个新值并释放旧值 – 真正的东西从第6行开始

  • ARC对弱本地(第6行, 0x7fff5fbfedc8 )使用隐藏variables(第8行, 0x7fff5fbfedd0

  • ARC并没有像上面那样将这个隐藏variables分配给retain / autorelease。 你可以在第7行看到autorelease,但我的Breadcrumbs错过了retain – 但是第8行的2的所有权显示它已经发生。

  • 有两个自动释放,所以当池被排空(第14和16行)时,必须有两个相应的释放 – 只有一个对应的释放(第15行),因为另一个对象( 0x100427d20 )被instance引用,ARC清除我们的ByRef实例消失。

概要

  • 没有任何附加属性,ARC将为本地(推断强)variables做参照(推断自动释放)作为parameter passing正确的东西。 (“local”包含当前方法的参数。)

  • 这是由ARC使用逐写回传实现的, 只有在遵循“out”参数模式的情况下才有效。 如果你想存储通过的参考以后使用,你需要自己做更多的事情。

  • 如果您希望通过引用传递实例variables,则需要将它们复制到本地,或使用__strong指定接收参数types。

  • 传递回写也适用于__weak本地人。

希望有所帮助。


附录2016年4月: __blockvariables

在Heath Borders的评论中,

如果我的本地variables是__blocktypes呢? 我敢肯定,这种情况是一样的实例variables,因为我需要将它们复制到当地人,或使用__strong属性接收参数types,但我很好奇别人的意见。

有趣的问题。

规范规定:

如果参数expression式不具有合法forms,则写回传递是不合法的:

&var ,其中var是自动存储持续时间的标量variables,带有可保留的对象指针types

(Objective-)C中的局部variables默认具有自动存储持续时间 – 当它们的封闭函数/方法/块被input/退出时,它们被自动创build和销毁。 在上面的回答中,当我们引用“局部variables”时,我们隐含地指的是具有自动存储持续时间的局部variables。

可以使用存储限定符存储类说明符声明局部variables,以更改variables的存储持续时间。 最常见的是static ; 具有静态存储持续时间的本地variables存在于整个程序的执行过程中,但是仅在本地范围内(直接)可访问。

如果您试图传递一个static本地variables与编写回传编译器将产生一个错误,指出该variables没有自动存储持续时间。 您必须以与实例variables(已分配存储持续时间 )相同的方式处理这些variables。

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

__block存储限定符与现有本地存储限定符autoregisterstatic是互斥的。 被__block限定的variables就好像在分配的存储中一样,这个存储在最后一次使用variables后自动恢复。

所以,一个__block局部variables的行为就好像它已经分配了存储时间,就像实例variables一样,所以通过写回传的规范,这样的variables不能被使用,因为它没有自动存储持续时间。

然而 ,在编写本文时(Xcode 7.2,Clang 7.0.2),当前使用的工具是__block限定的本地variables是通过回写支持的,并且与具有自动存储持续时间的处理相同 – 使用隐藏的__autoreleasing临时__autoreleasing

这似乎是无证的。

虽然说从编译的意义上说它是“安全的”,并且一旦编译完成,即使工具发生变化,代码仍然可以工作,并且以后不能再编译它们(至less不需要处理variables和实例variables一样必须被处理)。

可以接受的原因可以从对写回传递的限制 (强调增加)的基本原理中得到 :

合理

论证forms的限制有两个目的。 首先,它不可能将数组的地址传递给参数,以防止将“数组”参数错误推断为出参数的严重风险。 其次,由于下面的实现,用户将看到混淆锯齿问题的可能性要小得多,因为它们的存储到临时写回的位置在原始参数variables中没有立即出现。

没有技术上的原因,为什么实例variables不能通过回写来支持,但是由于别名可能会造成混淆。 __blockvariables位于自动分配和分配之间,所以当前工具编写者可能会select将它们与前者而不是后者进行分组,以进行回写。

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

这是完全合法的。 物业访问是无关紧要的; 传递一个指向对象的指针通常是用NSError*对象完成的。

声明你的方法的正确方法是

 - (returntype)doSomething:(Foo * __autoreleasing *)arg; 

这将其声明为指向__autoreleasing对象的指针,这基本上意味着被指向的对象被假定为已经被-autorelease

至于“进一步”,这不是根据ARC的问题。 你的线

 Foo * temp = self.bar; 

相当于

 __strong Foo *temp = self.bar; 

我希望这一点对你来说是显而易见的,因为这使得temp成为一个强有力的参考,因此只要variables存在,它就“拥有”它的价值。 换句话说,你可以说

 Foo *temp = self.bar; self.bar = nil; 

temp仍然有效。