2

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

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

所以实现看起来像这样;

用法

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

实现(WeakReference 类在这里找到,由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];
}

我读过一些关于块在超出其范围时被复制的内容,但坦率地说,阅读所有这些块语义现在有点让我头晕目眩。

这种解决问题的方法甚至可能吗?

4

3 回答 3

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,它要求观察者取消注册观察,我认为这是您应该遵循的一个好习惯。

于 2013-04-12T01:16:33.850 回答
0

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

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

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

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

于 2013-04-12T01:19:07.893 回答
0

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

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

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

来源在这里

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

于 2013-04-12T16:16:35.083 回答