我试图尽可能简洁地写下来,但这并不容易描述——所以感谢阅读=)
我是 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 的用户肯定会滥用它。
让我烦恼的是,我知道不必保留“自我”——它可以在我当前的实现中工作而无需保留它。所以我想告诉块他不需要保留自我——我,图书馆,应该告诉块,这样用户就不必考虑它了。
在我的研究中,我还没有找到这样做的方法。而且我想不出一种方法来改变我的架构以适应块的限制。
有人知道我能做些什么吗?
即使您还没有,也感谢您阅读本文——我知道这是一个冗长的问题;-)