Ste*_*eve 41 objective-c automatic-ref-counting
假设对象A有一个属性:
@property (nonatomic, strong) Foo * bar;
在实现中合成为:
@synthesize bar = _bar;
对象B操纵a Foo **,如本示例中对象A的调用:
Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;
doSomething:方法的正确声明是什么?此外,假设在我有机会设置属性之前可以释放对象Bbar(从而获取指向的实例的所有权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
CRD*_*CRD 144
ARC需要知道对象引用的所有权,以便它可以确定何时释放它等.对于任何变量(本地,实例或全局),ARC都有确定所有权的规则; 通过推理或显式属性.这相当于ARC需要程序员跟踪所有权.
但是如果你有一个变量的引用会发生什么?您不能(预先ARC)自己编写接受变量引用的代码,并且无论该变量的所有权如何都能正常工作 - 因为您无法知道是否需要发布等等.即您无法构造代码它适用于变量(在变化的意义上!)未知的所有权.
ARC面临同样的问题,其解决方案是推断或接受显式属性指定引用变量的所有权,然后要求调用者安排对要传递的适当所有权变量的引用.后一位可能需要使用隐藏的临时变量.这在说明书中被称为"最差解决方案",并且被称为"逐个写回".
问题的第一部分:
Run Code Online (Sandbox Code Playgroud)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在本地有效.使用实例变量执行此操作是不正确的(正如您在编辑中发现的那样).它也仅在假定参数在标准"out"模式下使用并且在doSomething:返回时已分配任何更新值时才有效.这两个都是因为pass-by-writeback的工作方式是"最不好的解决方案"的一部分......
总结:使用局部变量时,可以通过引用传递它们,以便在标准的"out"模式中使用,并使用ARC推断任何必需的属性等.
而不是Foo问题,我们将使用一种类型Breadcrumbs; 这基本上是一个包裹NSString追踪每一个init,retain,release,autorelease和dealloc(以及几乎你见下文),所以我们可以看到是怎么回事.怎么Breadcrumbs写不重要.
现在考虑以下课程:
@implementation ByRef
{
   Breadcrumbs *instance;                                // __strong inferred
}
一种更改引用传递的值的方法:
- (void) indirect:(Breadcrumbs **)byRef                  // __autoreleasing inferred
{
   *byRef = [Breadcrumbs newWith:@"banana"];
}
一个简单的包装器,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:调用局部变量的方法(想象力地称为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本地化自动释放池,这样我们就可以看到分配,发布和何时:
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 ...)和变量(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.它存储在local,其地址是0x7fff5fbfedc0,对象有1个owner(local).
ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1
这里是隐藏变量:因为indirect:需要引用自动释放变量,ARC创建了一个新变量,其地址为0x7fff5fbfedb8,并将对象reference(0x100176f30)复制到该变量中.
ark[2041:707] >>> 0x100427d10: init
ark[2041:707] >>> 0x100427d10: autorelease
ark[2041:707] indirect: returned
在indirect:创建一个新对象并在分配之前ARC自动释放它 - 因为传递的引用引用了一个自动释放变量.
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 (inlocal) to the object which it is managing. [In the last example below ARC does not elide the retain/autorelease.]
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-5 are just setting up instance - create a new value and release the old one - the real stuff starts at line 6
ARC uses a hidden variable (line 8, weakLocal) for weak locals (line 6, 0x7ffeefbfde40) as well
ARC has not elided the retain/autorelease on assigning to this hidden variable as it did above. You can see the autorelease on line 7 but my 0x7ffeefbfde58 missed the retainWeakReference - but the ownership of 2 on line 8 shows it occurred.
There are two autoreleases so there must be two corresponding releases when the pool is drained (lines 14 and 16) - there is only one corresponding dealloc (line 15) as the other object (retain) is referenced by 0x604000001060 and ARC cleans that up when our indirectWrapper 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 instance.
pass-by-writeback also works for 0x608000000d10 locals.
Hope that helps.
Addendum Apr 2016: ByRef variables
In the comments Heath Borders has asked:
What if my local variable is a
__strongtype? 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__weak, 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:
•
__block, where__blockis 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 __strong; 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 &var 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 var storage qualifier was introduced into (Objective-)C as part of blocks and the specification states:
The
staticstorage qualifier is mutually exclusive to the existing local storage qualifiersstatic,__block, and__block. Variables qualified byautoact as if they were in allocated storage and this storage is automatically recovered after last use of said variable.
So a register 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) static qualified local variables are supported by pass-by-writeback and are handle the same as those with automatic storage duration - a hidden __block 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
__blockqualified 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.
这是完全合法的.物业访问无关紧要; 将指针传递给对象通常用NSError*对象完成.
声明方法的正确方法是
- (returntype)doSomething:(Foo * __autoreleasing *)arg;
这将它声明为指向__autoreleasing对象的指针,这基本上意味着被指向的对象被假定为-autoreleased.
至于"此外",这不是ARC的问题.你的路线
Foo * temp = self.bar;
相当于
__strong Foo *temp = self.bar;
我希望你明白这是temp一个强有力的参考,因此只要变量存在,它就"拥有"它的价值.换句话说,你可以说
Foo *temp = self.bar;
self.bar = nil;
并且temp仍然有效.
| 归档时间: | 
 | 
| 查看次数: | 13957 次 | 
| 最近记录: |