【问题标题】:Recapturing Strong References to Weakly Referenced Objects Inside Blocks重新捕获对块内弱引用对象的强引用
【发布时间】:2013-09-08 09:58:43
【问题描述】:

我是一个出生的 Obj-C 程序员,只生活在后 ARC 世界。不过,为了我自己的效率,我最近决定通过 Apple 的 Transitioning to ARC Release Notes。在 ARC Introduces New Lifetime Qualifiers 部分中,有一个标题为使用生命周期限定符来避免强引用循环 的小节,它描述了潜在地使用限定符以避免潜在的保留循环的各种方法.

我的问题与最后两个示例有关。最后两个示例中的第一个使用了我经常使用的模式,以避免过早地从非主线程中释放 UIKit 对象:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
    [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};

在上面的示例中,weakMyViewController 对象是使用对myController 的弱引用创建的,因此引用weakMyViewController 的块可以使用它,并且在返回块时,weakMyViewController 可以安全地退出范围而不减少被引用的底层 UIKit 对象的引用计数。

不过,在下一个示例中,Apple 显示了以下“非平凡循环”代码:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;
    if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

在上面提供的“非平凡”示例中,相同的 __weak 限定符用于从块内引用 UIKit 对象,但随后代码创建了对同一对象的本地隐式 __strong 引用。然后对本地__strong 引用进行非零条件测试,然后对其进行操作。

我的两个问题是:

  1. Obj-C 程序员在实现第二种设计模式(与前者相反)时应该考虑什么?我不明白苹果关于“非平凡循环”的评论

  2. __strongweakMyController 的引用如何不增加原始myController 对象的保留计数?如果weakMyController 只是指向myController 指向的底层对象的指针,那么强指针(即stringMyController)不会增加底层对象的保留计数(myController 指向的对象) ?

【问题讨论】:

    标签: ios objective-c c macos automatic-ref-counting


    【解决方案1】:

    关于您的第一个示例,您认为让块文字仅保留对 myController 指向的实例的 weak 引用的动机并不完全正确。 weak 引用不会阻止对象释放,弱引用的目的是防止强引用循环(也称为保留循环)。在这种情况下,强引用循环将表现为myController 保持对存储在completionHandler 中的块的强引用,而块保持对myController 的强引用 - 两者都不会被释放(没有 un-在将来的某个时间设置completionHandler 属性)。因此,这里的动机与保持对象存活完全相反——它允许myController 在所有其他对它的引用不再存在时正常释放。

    第二个例子是第一个例子的扩展,但是通过将捕获的弱引用分配给块本地的强引用,我们可以确保只要控制器在块执行开始时仍然处于活动状态,它就会保持活动状态直到块执行结束。由于强引用仅作用于块,它不会创建强引用循环。换句话说,strongMyController 仅在块代码的范围内是本地的,并且不被块对象本身保留。

    现在,解决您的具体问题:

    1. 如果要确保块在其封闭范围内引用一个活动的、非零对象(如果该对象在块开始执行时该对象是活动的)完成执行,则可以采用这种方法。如果在执行块期间所有其他对对象的强引用可能会消失,您应该认真考虑使用它。
    2. 强引用确实会增加引用计数,并且是关键 - 当块代码的范围处于活动状态时,控制器将保持活动状态。这不会形成强引用循环,因为维护引用的不是块对象,而是块代码中使用的变量,该变量仅在块执行时存在。假设块不会继续执行到无穷大,那么循环将被打破。

    【讨论】:

    • 更精确一点(即 nitpick ;-),但我们正在寻求理解...),最后一句话并不完全正确:强引用循环 创建但在块完成时被破坏。循环本身并不坏,只是那些防止回收不需要的对象的循环。在这种情况下,循环很重要,如果在块执行过程中删除了对控制器的所有其他引用,则循环会使其保持活动状态,直到块完成。
    • @CRD strongMyController 是块体范围内的自动变量,但不被块对象本身保留。虽然它确实在块函数的堆栈帧中建立了对 VC 的强引用,但它并不是真正的循环,因为它不属于块,而是它的生命周期绑定到相关块函数的堆栈帧。但是,您正确地强调了强引用使 vc 保持活动状态直到执行结束。
    • 是的,我认为你赢了 ;-) 块和堆栈帧之间存在断开连接,因此它实际上不是一个循环,而只是另一个引用 - 如果需要,它可以使 VC 保持活动状态。
    • 感谢两位的帮助。现在晶莹剔透。其中一件事你必须阅读几遍才能完全掌握。我之前关于保持弱引用的措辞不佳的评论是在捕获具有强引用的 VC 的上下文中,该块计划在不是主线程的线程上运行 - TN -“释放问题”。始终保持弱引用,以便不会从差异线程调用 VC 的 dealloc。这在这里不是问题,因为大概总是从主线程调用 VC(UI Kit obj)的完成块。
    • @TaylorHalliday 对于我的回答不够清晰,我深表歉意。我已经对其进行了编辑,希望措辞更加清晰和具体。不过,我不完全确定 TN - "Deallocation Problem" 指的是什么。而且,您能否分享更多关于防止在另一个线程上调用dealloc 的信息,即需要防止异步块持有对视图控制器的最后一个强引用?
    猜你喜欢
    • 1970-01-01
    • 2018-07-11
    • 2013-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多