问题
我们正在使用Objective-C开发一个自定义的EventEmitter inspired消息系统。对于侦听器提供回调,我们应该要求使用blocks还是selectors?为什么?
作为使用第三方库的开发人员,您更喜欢使用哪种方式?哪种方式最符合苹果的发展轨迹、指南和实践?
背景
我们正在使用Objective-C开发全新的iOS SDK,其他第三方将使用该SDK将功能嵌入其应用程序中。我们SDK的一个重要部分将需要将事件通信给侦听器。
我知道有五种在Objective-C中进行回调的模式,其中三种不适用:
- NSNotificationCenter - 不能使用,因为它不能保证通知观察者的顺序,并且没有办法让观察者阻止其他观察者接收事件(就像 JavaScript 中的
stopPropagation()
一样)。 - Key-Value Observing - 看起来并不是很适合架构,因为我们真正拥有的是消息传递,而不总是与“状态”相关。
- Delegates and Data Sources - 在我们的情况下,通常会有许多侦听器,而不是单个可以正确称为委托的侦听器。
以下两个是备选方案:
这可能看起来像一个玄学的问题,但我觉得有一个客观的“正确”答案,只是我对Objective-C太没有经验了,无法确定。如果有更好的StackExchange网站来回答这个问题,请帮助我将其移动到那里。
更新#1-2013年4月
我们选择使用块作为指定事件处理程序的回调方式。我们很大程度上满意这个选择,并且不打算删除基于块的监听器支持。它确实有两个显着的缺点:内存管理和设计阻抗。
内存管理
块最容易在堆栈上使用。通过将长期存在的块复制到堆上来创建块会引入有趣的内存管理问题。
这段代码中,对包含对象的方法进行调用的块会隐式地增加 self
的引用计数。假设您的类具有 name
属性的 setter 方法,如果在块内调用 name = @"foo"
,编译器会将其视为 [self setName:@"foo"]
并保留 self
,以使其在块仍然存在时不被释放。
实现 EventEmitter 意味着拥有长期存在的块。为了防止隐式保留,Emitter 的使用者需要在块外创建一个对 self
的 __block
引用,例如:
__block *YourClass this = self;
[emitter on:@"eventName" callBlock:...
[this setName:@"foo"];...
}];
这种方法唯一的问题是在处理程序调用之前,
this
可能已被销毁。因此,当用户被销毁时,必须注销他们的侦听器。设计阻抗
经验丰富的Objective-C开发人员希望使用熟悉的模式与库进行交互。委托是一个非常熟悉的模式,因此规范的开发人员希望使用它。
幸运的是,委托模式和基于块的侦听器并不是相互排斥的。虽然我们的emitter必须能够处理来自许多地方的侦听器(只有一个委托将无法工作),但我们仍然可以公开一个接口,允许开发人员与emitter交互,就好像他们的类是委托一样。
我们还没有实现这个功能,但根据用户的请求,我们可能会这样做。
更新 #2 — 2013年10月
我已经不再从事引发这个问题的项目,很高兴地回到了我的JavaScript本土。
接管这个项目的聪明开发人员正确地决定完全放弃我们的自定义基于块的EventEmitter。即将发布的版本已经转向ReactiveCocoa。
这使得它们具有比我们先前的 EventEmitter 库更高级别的信号模式,并且允许它们更好地封装状态在信号处理程序中,而不是像我们的基于块的事件处理程序或类级别方法那样。