【问题标题】:Autoreleasing blocks in NSMutableArray retained by their creatorNSMutableArray 中的自动释放块由其创建者保留
【发布时间】:2013-04-15 00:14:04
【问题描述】:

我正在尝试基于 node.js EventEmitter 编写一个类别,它可以采用多个块,将它们弱存储在一个数组中,如果创建块的实例没有被释放,则稍后执行它们(在这种情况下,它们将从数组中删除)。这是为了不继续用旧的、未使用的块填充数组。

问题是这些块似乎是由类复制的,因此从未释放,即使创建块的实例已被释放。

所以实现看起来像这样;

用法

[object on:@"change" do:^(id slf, NSArray *args) {
    NSLog(@"something changed");
}];

实现(WeakReference 类发现 here,由 noa 提供)

- (void)on:(NSString *)eventType do:(Callback)callback
{
    NSMutableArray *callbacks = self.emitterEvents[eventType];
    __weak Callback wcb = callback;
    // Wrap the callback in NSValue subclass in order to reference it weakly
    WeakReference *cbr = [WeakReference weakReferenceWithObject:wcb];
    callbacks[callbacks.count] = cbr;
}

- (void)emit:(NSString *)eventType withArgs:(NSArray *)objArgs
{
    NSInteger idx = 0;
    NSMutableIndexSet *indices = [NSMutableIndexSet indexSet];
    callbacks = (NSMutableArray *)callbacks;
    for (WeakReference *cbv in callbacks) {
        __weak id cb = [cbv nonretainedObjectValue];
        if (cb) {
            Callback callback = (Callback)cb;
            __weak id slf = self;
            callback(slf, objArgs);
        } else {
            [indices addIndex:idx];
        }
        idx++;
    }
    [callbacks removeObjectsAtIndexes:indices];
}

我读到了一些关于 blocks being copied 的内容,但坦率地说,阅读所有这些块语义现在有点让我头晕目眩。

这种解决问题的方法是否可行?

【问题讨论】:

    标签: objective-c cocoa events objective-c-blocks


    【解决方案1】:

    在 Objective-C 中,块是对象,但与其他对象不同,它们是在堆栈上创建的。如果你想在它创建的范围之外使用块,你必须copy它。

    [object on:@"change" do:^(id slf, NSArray *args) {
        NSLog(@"something changed");
    }];
    

    在这里,您正在传递一个指向堆栈上的块的指针。一旦您当前的堆栈帧超出范围,您的块就消失了。您可以将副本传递给块,使调用者成为块的所有者,也可以将块复制到接收者中。

    如果您希望调用者拥有该块,那么您必须在调用者中保持对该块的强引用(例如,作为属性)。一旦调用者被解除分配,您将失去强引用并且弱引用被设置为 nil。

    【讨论】:

    • 在这种情况下调用者不需要复制它。决定是否需要复制它是被调用方法的工作。
    【解决方案2】:

    复制已复制的块与保留它相同,因此如果方法的调用者先复制块然后将其传递给方法,它应该可以按预期工作。但这意味着您不能简单地使用您在使用部分中描述的方法。

    你曾经这样使用过

    typeofblock block = ^(id slf, NSArray *args) {
        NSLog(@"something changed");
    };
    self.block = [block copy]
    [object on:@"change" do:self.block];
    

    要实际解决问题,您必须弄清楚拥有该块。 on:do: 的调用者,还是被调用的对象?

    听起来你想在调用者被释放时删除块,这意味着块的所有者是调用者。但是您的on:do: 方法不知道该块的所有者,并且在释放调用者时无法删除该块。

    一种方法是将块的所有者传递给方法,并在释放块时将其删除。这可以使用关联对象来完成。

    - (void)on:(NSString *)eventType do:(Callback)callback sender:(id)sender
    {
        // add the block to dict
        // somehow listen to dealloc of the sender and remove the block when it is called
    }
    

    另一种方法是添加新的方法来移除块,并调用dealloc或其他地方的方法手动移除块。

    您的方法类似于 KVO,它要求观察者取消注册观察,我认为您应该遵循一个很好的做法。

    【讨论】:

      【解决方案3】:

      感谢您的回答,我意识到我对块的管理方式有点偏离。我用不同的方法解决了这个问题,灵感来自Mike Ash's 使用块和自动取消引用的 KVO 实现,以及 xlc 在dealloc 中的建议。

      方法是这样的(如果您不想阅读整个gist):

      • 调用者对象将监听器分配给另一个对象on:event do:block with:caller
      • 发射器对象创建一个 Listener 实例,其中包含块的副本、对发射器的引用和事件类型
      • 发射器将复制的块添加到表内的数组中(按事件类型分组),在调用者上创建关联对象并附加侦听器
      • Emitter 方法-swizzles 调用者,并在其dealloc 中添加一个块,从而将自身从发射器中移除
      • 然后调用者可以选择处理从发出方法返回的侦听器实例,如果它想在自己释放之前手动停止侦听器

      Source here

      我不知道它是否可以安全使用,到目前为止我只在一个虚拟应用程序的单个线程上对其进行了测试。

      【讨论】:

        猜你喜欢
        • 2011-07-25
        • 2013-09-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-17
        • 1970-01-01
        • 2011-09-19
        相关资源
        最近更新 更多