NSMutableArray中的自动释放块由其创建者保留

时间:2013-04-11 21:00:05

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

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

问题是这些块似乎被类复制,因此即使创建块的实例被释放,也不会被释放。

所以实现看起来像这样;

用法

[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的一些内容,但坦率地说,阅读所有这些块语义,现在让我头晕目眩。

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

3 个答案:

答案 0 :(得分:0)

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

你已经像这样使用它了

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,它要求观察者注销观察,我认为这是一个很好的做法,你应该遵循。

答案 1 :(得分:0)

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

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

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

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

答案 2 :(得分:0)

感谢您的回答,我意识到我对如何管理块有点不满意。我用一种不同的方法解决了这个问题,灵感来自Mike Ash's KVO的实现与块和&自动解除引用,以及xlc关于在dealloc中执行此操作的建议。

方法与此类似(如果您不想阅读整个gist):

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

Source here

我不知道它是否安全使用,到目前为止我只在虚拟应用程序中的单个线程上测试过它。