【问题标题】:Objective-C block callbacks, weak references and disposed objectsObjective-C 块回调、弱引用和处置对象
【发布时间】:2014-12-02 23:03:26
【问题描述】:

我正在尝试使用 Objective-C 中的块来实现或多或少直接的回调机制。然而,我担心的是这将如何与 ARC 和内存管理一起工作。

考虑这样一种场景,我有一个视图控制器,它显示来自模型对象的一些相关信息,该模型对象支持我的块回调机制,只要该对象上的字段发生更改就会触发该机制(假设我不想为此使用 KVO )。视图控制器注册了一个引用 self 的块,并在该块内更新 UI 的各个方面。

现在假设视图消失了,但模型仍然存在于内存中,因为它可能在其他地方被引用。当对象更改并调用其注册的回调块时,现在会发生什么?大概这些块本身会持续存在,但是当我尝试引用 self 时,我会得到零。 (?)

我想要发生的事情是仅在其“目标”(本例中的视图控制器)仍然存在时才实际调用回调块。

第一个问题是,当视图控制器被释放时,有没有办法让块本身被自动释放?例如,如果我保留对块本身的弱引用会发生什么?我猜这是行不通的,因为这些块会立即消失,没有其他东西引用它们。

我的第二个想法是在块本身旁边保留对目标的弱引用,这样我可以在调用块之前检查目标引用以查看它是否活着(如果我发现目标已死我可以简单地删除相应的块)。

然后我的问题是我应该使用什么数据结构来存储这些——也许是一个结构或类,它维护对目标的弱引用和对块的强引用?

【问题讨论】:

    标签: objective-c automatic-ref-counting objective-c-blocks weak-references


    【解决方案1】:

    你的第二个想法其实很有趣。

    typedef void (^NotificationHandler)(Foo* bar, What* ever);
    
    @interface NotificationWrapper : NSObject
    @property (nonatomic, weak) id canary;
    @property (nonatomic, copy) NotificationHandler handler;
    @end
    

    您可以将这些包装器对象保存在一个简单的 NSMutableArray 中,可能位于通知处理程序的字典中。客户可以通过以下方式注册和取消注册通知:

    - (void)registerForNotification:(NSString*)notificationKey 
                             canary:(id)canary
                            handler:(NotificationHandler)handler;
    

    当需要触发通知时,请在调用处理程序之前检查金丝雀是否处于活动状态。如果金丝雀已死,请从通知处理程序列表中删除包装器。

    for (NotificationWrapper* wrapper in notificationHandlers)
    {
        if (wrapper.canary)
        {
             wrapper.handler(foo, bar);
        }
        else
        { 
             [deadHandlers addObject:wrapper];
        }
    }
    
    for (id handler in deadHandlers)
    {
        [notificationHandlers removeObject:handler];
    }
    

    那么这最终会给你带来什么?它使客户端不必手动取消注册其通知,这很好并且避免了 KVO 和NSNotificationCenter 的常见问题。但这并不能使客户免于处理他们提供的块中的潜在参考周期。

    【讨论】:

    • 喜欢你选择的变量名。 ;)
    【解决方案2】:

    我一直在使用以下代码 sn-p 并且运行良好:

    __weak id this = self;
    void (^blockObject)() = ^(){
    
        __strong typeof(self) strongThis = this; // Or __strong MyClass *strongThis = this;
        [strongThis someMethod];
    };
    

    如果我没记错的话,Apple 在其中一个 WWDC 上提出了这种方法。这样做的原因是,如果您只使用 __weak 引用,您的对象可能会释放,并且您的 __weak 引用将指向内存中的一些垃圾。但是,当您对 __weak 进行 __strong 引用时,在您的控制器释放后,您的 __strong 引用将变为 nil。随心所欲。

    【讨论】:

    • 虽然这当然是个好习惯,但如果self 被释放,我想避免调用回调根本,并让回调机制足够聪明以计算以某种方式解决这个问题,而不是将这个责任委托给每个区块。
    • 我认为将self 作为参数传递给我的回调寄存器方法(并将其存储在弱引用中)的想法有一定的希望。
    • 我同意你的观点,但有时如果你想从你的 dealloc 中释放所有块,你的代码会非常混乱。想象一下,您有一个带有 Web 服务的控制器,当然,它使用块来委派响应。 IE。如果您调用 8 个 Web 服务,其中每个服务都是某种 NSOperation,那么最终您将不得不保留 8 个实例。这对其他程序员来说也很烦人。
    • @chaiguy 我不知道为什么有必要这样做。不调用该块所获得的效率可以忽略不计,并且会产生一些混乱的代码。我会坚持最佳做法。
    • @AnthonyM 这不是为了效率,而是为了代码安全。如果我依靠块的实现者来验证目标是否仍然存在(他们应该这样做),它会引入错误/崩溃的可能性。如果回调系统的实现本身可以做到这一点,它就消除或减少了坏事发生的可能性。基本上,我正在接受该检查并将其向上移动一个级别,以便它自动应用于保证的每个块。
    【解决方案3】:

    我的第二个想法是在块本身旁边保留对目标的弱引用,这样我可以在调用块之前检查目标引用以查看它是否处于活动状态(如果我发现目标已死我可以简单地删除相应的块)。

    这似乎可行,但我认为推荐的最佳做法是:

    __weak typeof(self) weakSelf = self;
    returnType (^myBlock)(parameterTypes) = ^returnType(parameters) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            // your block implementation
        }
    };
    

    如果您真的不希望该块触发,那么您可以继续使用该块保持对目标的弱引用,但这似乎是混乱的代码。

    【讨论】:

    • 这个问题是如果你的对象被释放,你的weakSelf引用将不会是零。它将指向内存中的一些垃圾,并且 IF 条件为真。知道这一点,您最终可能会遇到一些崩溃。
    • 对不起,我忘了提到强我。立即编辑。
    • 弱引用永远不会指向垃圾,它将为零。去阅读编译器文档,都描述得很清楚。
    • 你在块内部定义了强引用,以确保它不会在中途死亡,而不是因为你害怕僵尸。
    【解决方案4】:

    现在假设视图消失了,但模型仍然存在于内存中 因为它可能在别处被引用。现在当对象发生了什么 更改并调用其注册的回调块?大概是 块自己会持续存在,但是当我尝试引用自己时 我会得到零。 (?)

    这对我来说真的没有意义。您说视图控制器使用引用self(视图控制器对象本身)注册了一些回调块。你说这些块本身仍然存在。因此,视图控制器将持续存在,因为它被活着的块保留。

    【讨论】:

    • 这是真的吗?带有闭包到视图控制器的块是否会无限期地保持该视图控制器的活动?这似乎不对,否则它会在任何使用块的代码中导致各种内存泄漏,不是吗?诚然,我并不是 100% 了解块闭包如何与 ARC 一起使用。
    • @chaiguy:是的,这对于块的工作方式非常重要。该块包含捕获变量的副本。捕获的变量是对视图控制器的强引用,因此该块具有对视图控制器的强引用。这并不意味着视图控制器无限期地保持活动状态——这意味着只要块处于活动状态,视图控制器就会保持活动状态。您应该返回并阅读有关块的更多信息。块拥有的强引用与任何其他对象拥有的引用没有什么不同。它不会比其他对象造成更多或更少的泄漏。
    • 但是如果块只引用了一个 __weak 自我版本(根据其他答案),这应该没问题,对吗?
    • @chaiguy: 是的,如果它使用一个__weak 变量,它是self 的副本(当然它不会被称为self,它会被称为weakSelf 或一些东西),那么当你在块中使用它时它可能是nil。但这不是您在问题中首先担心的吗?使用您现有的代码(使用self),您不可能有self 成为nil,这就是我要指出的。
    • 我更感兴趣的是避免在 self 为 nil 时调用块,但是很高兴知道不使用对 self 的弱引用可能会导致泄漏。
    猜你喜欢
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多