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

jtb*_*des 6 syntax memory-management objective-c ownership automatic-ref-counting

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

- (BOOL)tryWithError:(NSError *__autoreleasing *)err;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

但是如果我们用一个实例变量来做:

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

这给了我们错误

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

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

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

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

注意:将out关键字添加到参数类型将略微减少编译器的工作量,因为它不必将当前值读入临时变量 - 但这不会解决错误.

Tam*_*ese 1

指向 ivar 的指针无法在 ARC 下传递给 \xe2\x80\x9cid __autoreleasing *\xe2\x80\x9d 参数,因为这种传递写回的格式不正确。ARC规范中的相应部分列出了pass-by writeback的合法形式,这里唯一适用的是

\n\n
\n

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

\n
\n\n

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

\n\n

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

\n\n

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

\n\n

2) 一般来说,是否使用writeback值取决于方法的约定。这是编译器无法检查的。

\n\n

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

\n\n
NSError * __autoreleasing error;\nBOOL OK = [myObject performOperationWithError:&error];\nif (!OK) {\n    // use error\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

然而,这些__autoreleasing都很丑陋,所以编译器并__autoreleasing允许我们传递一个__strong(但本地的)变量(默认所有权),而不是强迫我们到处使用:

\n\n
NSError *error;\nBOOL OK = [myObject performOperationWithError:&error];\nif (!OK) {\n    // use error\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

根据文档,它被重写为:

\n\n
NSError * __strong error;\nNSError * __autoreleasing tmp = error;\nBOOL OK = [myObject performOperationWithError:&tmp];\nerror = tmp;\nif (!OK) {\n    // use error\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

完全不是问题,错误只会在成功的情况下使用。

\n\n

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

\n\n
NSError * __autoreleasing tmp = _error;\nBOOL OK = [myObject performOperationWithError:&tmp];\n_error = tmp;\nif (!OK) {\n    // use error\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里的问题是,写回总是tmp会被使用(分配给实例变量),忽略了写回只能在错误情况下使用的方法的约定(或者一般来说,无论该方法的文档说什么)。将最后一个错误分配给实例变量的安全方法是_error

\n\n
NSError * __autoreleasing tmp = _error;\nBOOL OK = [myObject performOperationWithError:&tmp];\nif (!OK) {\n    _error = tmp; \n    // use error\n} else {\n    _error = nil; // Make sure that _error is nil if there was no error.\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这仅适用于返回错误的 Cocoa 方法的约定。一般来说,编译器无法告诉方法将如何处理id *:可能有使用不同约定的旧方法。因此,如果您确实想将写回存储在__strong实例变量中,那么您目前必须自己多走一英里,而且我不希望这种情况发生改变。

\n