为什么我们必须将__block变量设置为nil?

时间:2012-06-12 13:54:10

标签: objective-c xcode4.3

来自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将不再被再次使用,即块何时到期。

2 个答案:

答案 0 :(得分:14)

当您拥有此表单的代码时:

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并且保留周期被破坏。这允许释放两个对象。

一个具体的例子是使用NSOperation的{​​{1}}属性。如果使用completionBlock来访问操作的结果,则需要中断创建的保留周期:

completionBlock

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

答案 1 :(得分:0)

我更喜欢这个解决方案

typeof(self) __weak weakSelf = self;
self.rotationBlock = ^{
    typeof (weakSelf) __strong self = weakSelf;

    [self yourCodeThatReferenceSelf];
};

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