【问题标题】:Why do we have to set __block variable to nil?为什么我们必须将 __block 变量设置为 nil?
【发布时间】:2012-06-15 10:09:28
【问题描述】:

来自Transitioning to ARC Release Notes

使用生命周期限定符来避免强引用循环

您可以使用生命周期限定符来避免强引用循环。为了 例如,通常如果您有一个以 父子层次结构和父母需要参考他们的孩子和 反之亦然,那么您就可以使父母与孩子的关系变得牢固,并且 亲子关系薄弱。其他情况可能更多 微妙的,尤其是当它们涉及块对象时。

在手动引用计数模式下,__block id x;具有不 保留x。在 ARC 模式下,__block id x; 默认保留x(只需 像所有其他值一样)。获取手动引用计数模式 ARC下的行为,你可以使用__unsafe_unretained __block id x;。 然而,正如名称 __unsafe_unretained 所暗示的那样,拥有一个 非保留变量是危险的(因为它可以悬挂)并且是 因此气馁。两个更好的选择是使用__weak(如果 您不需要支持 iOS 4 或 OS X v10.6),或设置__block 值为nil 以打破保留周期。

好的,那么__block 变量有什么不同?

为什么在这里设置为nil__block 变量是否保留了两次?谁持有所有参考资料?块?堆?堆栈?线程?什么?

以下代码片段使用有时用于手动引用计数的模式来说明此问题。

MyViewController *myController = [[MyViewController alloc] init…];

// ...

myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};

[self presentViewController:myController animated:YES completion:^{
   [myController release];
}];

如前所述,您可以使用 __block 限定符并在完成处理程序中将 myController 变量设置为 nil

MyViewController * __block myController = [[MyViewController alloc] init…]; //Why use __block. my controller is not changed at all

// ...

myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];

    myController = nil; //Why set to nil here? Is __block variable retained twice? Who hold all the reference? The block? The heap? The stack? The thread? The what?
};

还有为什么编译器没有将myController 设置为nil。为什么我们必须这样做?似乎编译器知道什么时候 myController 将不再被使用,即块何时过期。

【问题讨论】:

    标签: objective-c xcode4.3


    【解决方案1】:

    当你有这种形式的代码时:

    object.block = ^{
        // reference object from inside the block
        [object someMethodOrProperty];
    };
    

    object 将保留或复制您给它的块。但是块本身也将保留object,因为它在块内被强烈引用。这是一个保留周期。即使在块执行完毕后,引用循环仍然存在,对象和块都不能被释放。请记住,一个块可以被多次调用,所以它不能在执行完一次后就忘记它引用的所有变量。

    要打破这个循环,您可以将object 定义为__block 变量,它允许您从块内部更改其值,例如将其更改为nil 以打破循环:

    __block id object = ...;
    object.block = ^{
        // reference object from inside the block
        [object someMethodOrProperty];
    
        object = nil;
        // At this point, the block no longer retains object, so the cycle is broken
    };
    

    当我们在块的末尾将object 分配给nil 时,块将不再保留object,并且保留循环被打破。这允许释放两个对象。

    一个具体的例子是NSOperationcompletionBlock 属性。如果您使用completionBlock 访问操作的结果,则需要中断创建的保留循环:

    __block NSOperation *op = [self operationForProcessingSomeData];
    op.completionBlock = ^{
        // since we strongly reference op here, a retain cycle is created
        [self operationFinishedWithData:op.processedData];
    
        // break the retain cycle!
        op = nil;
    }
    

    如文档所述,您还可以使用许多其他技术来打破这些保留周期。例如,您需要在非 ARC 代码中使用与在 ARC 代码中不同的技术。

    【讨论】:

    • "但是块本身也会保留对象,因为它在块内被强引用。"为什么?关闭。
    • 添加 __block 有什么不同?
    • 当一个块捕获一个指向objective-c对象的指针时,该对象将被保留,除非你使用__weak__unsafe_unretained(或__block在非ARC代码中)。跨度>
    • 在非 ARC 代码中,__block 变量不会被保留。然而,这是一个实现细节,而不是__block 的语义。 __block 只允许您从块内更改变量的值。在 ARC 代码中,__block 变量像非__block 变量一样被保留。所以我们将object 变量定义为__block,这样我们就可以从块内部将它设置为nil 并打破保留循环。脑痛了吗?
    • 其实很常见。 NSOperation *operation = ...; operation.completionBlock = ^{ // code to execute when the operation is finished, which often will try to access operation } 。这只是一个例子。
    【解决方案2】:

    我更喜欢这个解决方案

    typeof(self) __weak weakSelf = self;
    self.rotationBlock = ^{
        typeof (weakSelf) __strong self = weakSelf;
    
        [self yourCodeThatReferenceSelf];
    };
    

    发生的情况是该块将捕获 self 作为 weak 引用,并且不会有保留周期。然后在代码运行之前将块内的 self 重新定义为 __strong self = weakSelf。这可以防止在你的块运行时释放 self。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-13
      • 2017-05-26
      • 2020-05-31
      • 1970-01-01
      相关资源
      最近更新 更多