【问题标题】:Why can't the address of an ivar be passed to an "id __autoreleasing *" argument under ARC?为什么不能将 ivar 的地址传递给 ARC 下的“id __autoreleasing *”参数?
【发布时间】:2012-12-21 03:03:15
【问题描述】:

在 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 关键字添加到参数类型will reduce the compiler's work slightly,因为它不必将当前值读入临时变量——但这并不能解决错误。子>

【问题讨论】:

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


    【解决方案1】:

    不能将指向 ivar 的指针传递给 ARC 下的“id __autoreleasing *”参数,因为这种传递式写回的格式不正确。 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) 一般来说,是否应该使用回写值取决于方法的约定。这是编译器无法检查的。

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

    【讨论】:

    • “你不能这样做,因为它是被禁止的”并没有解释很多原因:/
    • @Norswap 我在这方面添加了更多信息,希望对您有所帮助。
    • 非常感谢!但我不明白的是:如果可以将强局部变量传递给* __autoreleasing * 参数,为什么不能传递强实例变量? “Transitioning to ARC Release Notes”文档(在“Variable Qualifiers”一节中)解释了传递强局部变量时会发生什么。我不明白为什么不能将相同的逻辑应用于强实例变量。
    • 一个 __strong 局部变量在堆栈上并且是短暂的。如果方法通过写回将指向自动释放对象的指针写入此类局部变量,则代码优化会阻止对象上的保留/释放。由于指向的对象在自动释放池中,因此不需要保留/释放,因此它将比堆栈帧存活更长的时间。也就是说,如果不进行优化,您可以构建我的答案中的第二个代码 sn-p 导致在调用一个实施不佳的 MRR 方法时崩溃的情况,而第一个代码可以正常工作。
    • 根据我之前提到的文档,您的第二个 sn-p 在 ARC 下完全有效,无论是否优化。我现在明白为什么它会给 MRR 带来问题。我不明白为什么在 ivar 的情况下,ARC 无法生成与局部变量相同的代码(请参阅文档)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-03
    • 2011-11-16
    • 1970-01-01
    相关资源
    最近更新 更多