6

在 ARC 下,out 参数采用以下形式(默认情况下;这等效于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关键字添加到参数类型会稍微减少编译器的工作,因为它不必将当前值读入临时变量——但这并不能解决错误。

4

1 回答 1

1

不能将指向 ivar 的指针传递给 ARC 下的“id __autoreleasing *”参数,因为这种 pass-by-writeback 格式不正确。ARC 规范中的相应部分列出了 pass-by-writeback 的合法形式,此处唯一适用的是

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

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

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

1)您应该只查看失败情况下的错误写回。在成功的情况下,根本无法保证错误指针中的内容。

2) 一般情况下,是否使用回写值取决于方法的约定。这是编译器无法检查的。

这是将&error( NSError * __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

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实例变量中,您目前必须自己加倍努力,我不希望这会改变。

于 2013-05-30T14:28:58.613 回答