5

我试图尽可能简洁地写下来,但这并不容易描述——所以感谢阅读=)

我是 Open Source iPhone Framework Sparrow的主要开发者。Sparrow 以 Flash AS3 库为模型,因此具有与 AS3 类似的事件系统。目前,该系统通过指定选择器来工作——但我希望通过允许使用块作为事件侦听器来扩展该系统。但是,我遇到了内存管理问题。

我将向您展示一个典型的事件用例——就像现在处理的那样。

// init-method of a display object, inheriting from 
// the base event dispatcher class
- (id)init
{
    if (self = [super init])
    {
        // the method 'addEventListener...' is defined in the base class
        [self addEventListener:@selector(onAddedToStage:)
                      atObject:self
                       forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
    }
    return self;
}

// the corresponding event listener
- (void)onAddedToStage:(SPEvent *)event
{
    [self startAnimations]; // call some method of self
}

这很简单:当一个对象被添加到显示列表时,它会收到一个事件。目前,基类将事件监听器记录在一个 NSInvocation-objects 数组中。NSInvocation 以保留其目标和参数的方式创建。(用户可以这样做,但在 99% 的情况下,这不是必需的)。

不保留这些对象是有意识的选择:否则,即使用户在 dealloc 方法中删除了事件侦听器,上面的代码也会导致内存泄漏!原因如下:

- (id)init
{
    if (self = [super init])
    {
        // [self addEventListener: ...] would somehow cause:
        [self retain]; // (A)
    }
    return self;
}

// the corresponding event listener
- (void)dealloc
{
    // [self removeEventListener...] would cause:
    [self release]; // (B)
    [super dealloc];
}

乍一看,这似乎很好:init 方法中的保留与 dealloc 方法中的释放配对。但是,这不起作用,因为永远不会调用 dealloc 方法,因为保留计数永远不会达到零!

正如我所说,'addEventListener...'-方法确实出于这个原因,在其默认版本中不保留任何内容。由于事件的工作方式(它们几乎总是由“自我”或子对象分派,无论如何都会保留),这不是问题。

然而,现在我们来到了问题的核心部分:我不能用积木来做到这一点。查看事件处理的块变体,我希望它具有:

- (id)init
{
    if (self = [super init])
    {
        [self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event)
        {
            [self startAnimations];
        }];
    }
    return self;
}

这看起来很棒,而且很容易使用。但是:当用户在“self”上调用方法或使用块中的成员变量时——嗯,几乎总是这样——块将自动保留“self”,并且对象永远不会被释放.

现在,我知道任何用户都可以通过对 self 进行 __block 引用来纠正这个问题,如下所示:

__block id blockSelf = self;
[self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event)
{
    [blockSelf startAnimations];
}];

但是,老实说,我相信几乎所有用户都不知道这样做或忘记这样做。一个 API 不仅要易于使用,而且要难以误用,这显然违反了这一原则。API 的用户肯定会滥用它。

让我烦恼的是,我知道不必保留“自我”——它可以在我当前的实现中工作而无需保留它。所以想告诉块他不需要保留自我——我,图书馆,应该告诉块,这样用户就不必考虑它了。

在我的研究中,我还没有找到这样做的方法。而且我想不出一种方法来改变我的架构以适应块的限制。

有人知道我能做些什么吗?
即使您还没有,也感谢您阅读本文——我知道这是一个冗长的问题;-)

4

2 回答 2

5

我与 Apple 支持讨论了这个话题,他们告诉了我我的预期:目前,没有办法让我告诉块它不应该保留 self。但是,他们提供了两种解决问题的方法。

第一种是将 self 作为参数传递给块而不是块变量。API 将变为:

[self addEventListenerForType:ADDED_TO_STAGE 
                        block:^(id selfReference, SPEvent *event)

因此,API 将负责传入(非保留的)自我。开发人员仍然必须被告知他们必须使用这个引用而不是 self,但至少它会很容易使用。

Apple 在这种情况下使用的另一个解决方案是提供一个独立于 release/dealloc 的方法来关闭侦听器。NSTimer 的“无效”方法就是一个很好的例子。它是由于 NSRunLoop 和 NSTimer 之间的内存循环而创建的(按设计)。

于 2010-11-27T20:05:50.523 回答
1

我认为设计从根本上是有缺陷的,有障碍或没有障碍。在对象完全初始化之前,您正在将对象添加为事件侦听器。同样,对象在被释放时仍然可以接收事件。事件侦听器的添加和删除应该与分配/解除分配 IMO 分离。

至于你眼前的问题,你可能不需要担心。您遇到了问题,因为您通过在块中引用对象来隐式地(间接地)添加对对象自身的引用。请记住,提供块的任何其他人都不太可能引用生成事件的对象或其实例变量,因为它们不在定义块的范围内。如果它们是(例如在子类中),那么除了记录问题外,您无能为力。

降低问题可能性的一种方法是将生成事件的对象作为 SPEvent 参数的属性提供,并在块中使用它而不是引用 self. 无论如何,您都需要这样做,因为不需要在生成事件的对象在范围内的上下文中创建块。

于 2010-10-20T10:43:00.753 回答