为什么不能将一个ivar的地址传递给ARC下的“id __autoreleasing *”参数?

时间:2013-01-07 11:31:16

标签: objective-c memory-management syntax automatic-ref-counting ownership

在ARC下,out-parameter采用以下形式(默认情况下;这相当于NSError **):

- (BOOL)tryWithError:(NSError *__autoreleasing *)err;

Transitioning to ARC Release Notes开始,如果我们传递__strong局部变量的地址,编译器将创建一个临时变量并生成以下代码:

NSError *error; // strong
BOOL ok = [myObject tryWithError:&error];

// translated to

NSError *__strong error;
NSError *__autoreleasing tmp = error;
BOOL ok = [myObject tryWithError:&tmp];
error = tmp;

但是,如果我们使用实例变量:

@implementation Foo {
    NSError *_error; // strong
}
- (void)bar
{
    [myObject tryWithError:&_error];
}
...

这给了我们错误

  

将非本地对象的地址传递给__autoreleasing参数以进行回写。

为什么这会无效?编译器是否只能将此类代码自动转换为此内容?

- (void)bar
{
    NSError *__autoreleasing tmp = _error;
    [myObject tryWithError:&tmp];
    _error = tmp;
}

毕竟,这就是我将要编写的解决问题的方法!

注意:将out关键字添加到参数类型will reduce the compiler's work slightly,因为它不必将当前值读入临时变量 - 但这并不需要考虑错误。

1 个答案:

答案 0 :(得分:1)

指向ivar的指针无法传递给ARC下的“id __autoreleasing *”参数,因为那种pass-by-writeback格式不正确。 respective section in the ARC specification列出了pass-by-writeback的合法形式,这里唯一适用的是

  

& var,其中var是自动存储持续时间的标量变量   使用可保留对象

,因此只允许自动存储持续时间(局部变量)。

为什么这是无效的:我很确定这里的原因是与旧代码的兼容性:

1)You should only look at the error writeback in the failure case。在成功的情况下,根本无法保证错误指针内部的内容。

2)通常,是否应该使用回写值取决于方法的合同。这是编译器无法检查的内容。

这是将&errorNSError * __autoreleasing *)的类型与回写类型NSError **(被解释为NSError * __autoreleasing *)匹配的代码版本。如果ok为YES,则不会触及错误值。

NSError * __autoreleasing error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
    // use error
}

然而,那些__autoreleasing是丑陋的,所以编译器允许我们传递__autoreleasing(但是本地)变量,而不是强迫我们在整个地方使用__strong。 (默认所有权):

NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
    // use error
}

根据文档,这被重写为:

NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
    // use error
}

根本不是问题,错误只会在成功案例中使用。

现在让我们看一下__strong实例变量_error。为什么编译器不允许这样做?这是重写的样子:

NSError * __autoreleasing tmp = _error;
BOOL OK = [myObject performOperationWithError:&tmp];
_error = tmp;
if (!OK) {
    // use error
}

这里的问题是tmp中的回写将总是被使用(分配给实例变量_error),忽略方法的契约< / em>只应在错误情况下使用回写(或者通常无论方法的文档说什么)。将最后一个错误分配给实例变量的安全方法是

NSError * __autoreleasing tmp = _error;
BOOL OK = [myObject performOperationWithError:&tmp];
if (!OK) {
    _error = tmp; 
    // use error
} else {
    _error = nil; // Make sure that _error is nil if there was no error.
}

这对于返回错误的Cocoa方法的惯例是正确的。通常,编译器无法告诉方法对id *的作用:可能存在使用不同约定的旧方法。因此,如果您真的想将回写存储在__strong实例变量中,那么您当前必须自己走得更远,我不希望这会改变。