处理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的代码很好。 我们推断temp
是strong
,一些幕后的东西恰好通过参考doSomething:
- 什么是doSomething:方法的正确声明?
- (void) doSomething:(Foo **)byRefFoo
ARC推断byRefFoo
的types是Foo * __autoreleasing *
– 对自动释放引用的引用。 这是“写回传”所要求的。
这个代码只是有效的,因为temp
是一个本地的。 用一个实例variables来做这件事是不正确的(正如你在编辑中发现的那样)。 假设参数在标准“out”模式下使用,并且在doSomething:
返回时已经分配了任何更新的值,这也是唯一有效的。 这两者都是因为写回传的方式是“最不好的解决scheme”的一部分。
总结 :当使用局部variables时,它们可以通过引用传递,用于标准的“out”模式,用ARC推断任何必需的属性等。
在胡德之下
而不是问题的Foo
,我们将使用typesBreadcrumbs
; 这本质上是一个包装的NSString
,它跟踪每个init
, retain
, release
, autorelease
和dealloc
(就像你将在下面看到的一样),这样我们就可以看到发生了什么。 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月: __block
variables
在Heath Borders的评论中,
如果我的本地variables是
__block
types呢? 我敢肯定,这种情况是一样的实例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
存储限定符与现有本地存储限定符auto
,register
和static
是互斥的。 被__block
限定的variables就好像在分配的存储中一样,这个存储在最后一次使用variables后自动恢复。
所以,一个__block
局部variables的行为就好像它已经分配了存储时间,就像实例variables一样,所以通过写回传的规范,这样的variables不能被使用,因为它没有自动存储持续时间。
然而 ,在编写本文时(Xcode 7.2,Clang 7.0.2),当前使用的工具是__block
限定的本地variables是通过回写支持的,并且与具有自动存储持续时间的处理相同 – 使用隐藏的__autoreleasing
临时__autoreleasing
。
这似乎是无证的。
虽然说从编译的意义上说它是“安全的”,并且一旦编译完成,即使工具发生变化,代码仍然可以工作,并且以后不能再编译它们(至less不需要处理variables和实例variables一样必须被处理)。
可以接受的原因可以从对写回传递的限制 (强调增加)的基本原理中得到 :
合理
论证forms的限制有两个目的。 首先,它不可能将数组的地址传递给参数,以防止将“数组”参数错误推断为出参数的严重风险。 其次,由于下面的实现,用户将看到混淆锯齿问题的可能性要小得多,因为它们的存储到临时写回的位置在原始参数variables中没有立即出现。
没有技术上的原因,为什么实例variables不能通过回写来支持,但是由于别名可能会造成混淆。 __block
variables位于自动分配和分配之间,所以当前工具编写者可能会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
仍然有效。