13

我希望对单个对象的多个事件(一对 N 关系)有多个观察者。

实现此任务的机制由NSNotificationCenter. 当用于我的问题时,该机制看起来有点矫枉过正。

我将如何在不使用的情况下手动完成NSNotificationCenter

- (void)addDelegate:(id<DelegateProtocol>)delegate;
- (void)removeDelegate:(id<DelegateProtocol>)delegate;

从我的对象中添加和删除观察者。

- (void)someEventFired:(NSObject<NSCopying> *)eventData
{
    for (id delegate in delegates) {
        NSObject *data = [eventData copy];
        [delegate someEventFired:data];
    }
}

这种机制直接且易于实现,无需对象共享额外的字符串。

  • 除了NSNotificationCenter?
  • 什么时候应该NSNotificationCenter使用,什么时候不应该使用?
  • 什么时候应该使用像我在这里建议的那样的实现,什么时候不应该使用?
4

7 回答 7

14

按照惯例,委托可能应该只用于 1:1 的关系。如果您确实需要此类功能的 1:N 关系,您有两种选择:

  1. 正如你所提到的,NSNotificationCenter.
  2. 键值观察(也称为 KVO)。

如果您只关心对象的特定属性何时更改,则 KVO 是合适的。否则,你真的应该考虑使用NSNotificationCenter. 您甚至可以通过将该对象传递给方法,仅在特定对象发布该通知时才收到通知addObserver:selector:name:object:

AppleNSNotification在类似的场景中使用(例如为 定义的通知UITextField,包括UITextFieldTextDidBeginEditingNotificationUITextFieldTextDidChangeNotificationUITextFieldTextDidEndEditingNotification)。

于 2012-05-31T15:28:20.203 回答
2

使用通知就是广播:1 个发送者只是发送一个信息,而谁曾经收听过,就会收到它。很像一个广播电台,没有频道回来(让我们暂时忘记电话)

代表团是不同的东西。要求委托人做某事的对象通常需要该请求的结果,因此委托是一对一的通信,始终由对象而不是委托发起(而对象可以具有可以调用来通知对象发起通信,即[tableView reloadData])。

因此,如果发送方需要取回数据,那就是委托。如果发件人在广播后不关心任何事情,请使用通知。

如果您遇到这种情况,您需要委托,但有几个对象应该实现该协议。你应该有 1 个委托,它持有对其他对象的引用并代表发送者调用方法——或者你可以使用块。

于 2012-05-31T15:32:21.417 回答
1

NSNotificationCenter 对于您的建议并不过分,它完全是正确的解决方案。它可以防止被观察对象必须知道或关心它的观察者,从而使您的代码更加松散耦合和清洁。

通知名称的共享字符串是微不足道的,如果您的观察者需要导入此标头来完成他们的工作,它们可以在共享常量文件或被观察对象的标头中定义。

于 2012-05-31T15:27:10.270 回答
1

您提出的解决方案既不比使用 NSNotificationCenter 简单,也不是线程安全的。

为了使您的解决方案线程安全,您需要提供一种机制来防止在事件调度 for 循环运行时委托数组发生变化。

您的解决方案还要求您维护班级中的代表数组。使用 NotificationCenter,您可以简单地使用默认中心,而无需在您的类中实现添加/删除方法。相反,实例可以注册自己以接收它们认为最合适的通知(选择器/块、队列、源)。您的源类不必担心这些细节。它只需要将自己注册为指定类型的通知源。使用块来处理通知真的很方便。

通知中心的替代方法是使用 Key-Value-Observing(如果满足您的用例需求)。

最终,您决定使用的机制取决于它如何最好地应用于您的特定用例。

于 2012-05-31T15:46:08.947 回答
0

1 对 N 的委托关系没有意义。看一下

- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row

例如。如果这个对象真的有 n 个委托怎么办?它应该如何决定应该使用从所有代表那里获得的 n 个视图中的哪一个?代表正是这种一对一的原则。

NSNotificationCenter 是正确的方法。只需使用

addObserver:selector:name:object:

分别

postNotification:

这绝对不是太多的代码。由于中心处理所有呼叫,这对您来说非常容易。

于 2012-05-31T15:30:01.573 回答
0

您不想将 NSNotificationCenter 用于系统范围事件以外的任何事情(例如键盘的外观或一些类似事件)。原因是它完全不是类型安全的,可以使所有内容都依赖于所有内容,并且您不再获得编译时检查或使用搜索结果。

在我看来,KVO 不应该用于观察您正在收听的对象之外的变化,因为它有类似的缺点(没有编译时间检查,如果您没有正确删除侦听器或注册两次,则会崩溃)。

在我看来,您提出的 addDelegate/removeDelegate 模式完全是正确的路径,因为它具有维护类型安全和编译器检查并使依赖关系明确的优势。唯一的问题是 Apple 没有为这种模式提供开箱即用的解决方案,因为您需要一个弱保留其元素的集合类型以避免保留循环。

但是,请参阅我的BMCommons 框架中的代码,它使用BMNullableArray和宏巧妙地解决了这个问题。有关这些宏的定义,请参见BMCore.h标头:

BM_LISTENER_METHOD_DECLARATION(protocol)
BM_LISTENER_METHOD_IMPLEMENTATION(protocol)

该实现确保同一个侦听器永远不会被添加两次,并且侦听器被弱保留,即使他们忘记在释放时注销自己也不会导致任何崩溃(尽管我更喜欢用断言来捕获这个条件,因为这是一个编程错误)。

于 2017-11-24T08:20:36.470 回答
-4

我说 NSNotificationCenter 应该始终在委托模型上使用,除非在您查询委托的信息(例如-webView:shouldLoadRequest:)的情况下。与尝试使用委托相比,它更稳定,更易于实现,并且代码更简洁。另一种选择是块,这可能很好,但在内存管理方面可能会很痛苦。

最后,这取决于你,但我认为 NSNotificationCenter 是几乎在任何情况下的最佳方式,即使只是用于多个观察者功能。

于 2012-05-31T15:27:03.177 回答