Notification Programming
通知机制避免了事件发生时传递消息发生紧耦合,在传递消息的同时保持了对象之间的独立,观察者会对感兴趣的事件作出反应,发布通知的对象不需要了解观察者的任何信息,而观察者至少需要知道通知名字和通知对象。
通知中心将通知同步发送给观察者。 换句话说,同步发布通知时,语句执行的控制权不会移交给发布者,直到所有观察者都收到并处理了通知。 要异步发送通知,请使用Notification Queues,在通知队列中进行了介绍。在多线程应用程序中,通知始终在发布通知的这个线程中传递。
/*
# 请记住,例如 可以通过使用协议等设计模式来松散耦合,特别是模型层和控制层的事件交流——控制器和/或模型层对象。
- 那什么时候使用通知中心呢(原则)
1. 当沟通需要反复发生并且保持一致时。(决定性的原因,高速公路,可以快速的交流)
2. 当使用一对多或多对多沟通时,通知中心是多对多。
3. 当应用程序的两个或多个组件之间需要通信时,它们之间没有正式的连接。
*/
So! Notification Center. Now you know… Here’s a quick summary:
- First, register an observer for a notification with: addObserver(_:selector:name:object:)
- Then, post a notification with post(name:object:userInfo:) …
- … after which your selector is called
- And don’t forget to remove the observer with removeObserver(...)
用通知中心方法注册本地通知观察者
通知中心管理着注册着观察者的表并通过通知调用链将收到的通知参数发送给满足特定条件的观察者。未注册的通知者,不能使用通知调用链。
未注册的通知者,不能使用通知调用链。使用NotificationCenter的关键是您要在要观察的通知发生之前注册观察者。 当通知发布到通知中心,并且您尚未观察到时,您显然会错过该通知。
Tips:通知调用链 ——— 观察者先在通知中心注册,然后被观察对象发生变化就制定通知发送规则,根据规则再发送通知到通知中心,通知中心再在注册表中查找对应的已经注册的观察者,然后调用相应观察者的通知响应方法。完成这个过程后会执行post方法下面的语句,除非是异步而且还没发送通知中心正等待中才可以直接执行post下面的语句或者继续合并通知队列,跳过调用相应观察者的响应方法。
通知调用链根据注册通知的方法决定如何在线程中传递,提供观察者响应方法的形参的注册通知方法主要用于主线程处理通知,闭包形式参数的注册通知方法在处理异步发送来通知的时候比较方便。
一般在viewDidLoad() and dealloc, or viewWillAppear(:) and viewWillDisappear(:)注册和移除通知,观察者必须先被移除,否则可能会向不存在的对象发送消息。(移除可以一次移除多个或者指定移除某一个)
NotificationCenter.default.addObserver(self, selector: #selector(handleChangeNotification(_:)), name: Store.changedNotification, object: nil);
/*
object形式参数可以是任意的,主要用来过滤通知的发送对象(注册时没有过滤),包括注册的object形式参数(这里是帮助响应通知方法直接过滤了),一般都是指发送通知sender或者nil; userInfo形式参数 send extra data with the notification.🍬post方法中的参数会被系统包装成notification对象发送给注册的观察者对象。
*/
通知中心NotificationCenter.default(发送通知方式之一)
NotificationCenter.default.post(name: Store.changedNotification, object: notifying, userInfo: userInfo);
使用NotificationCenter发布的所有通知都是同步传递的,这意味着所有观察者会同时收到通知并执行所有代码,然后将控制权传递回通知的发布者(接下来其线程上的剩余的语句执行)。post过程中会自动生成Notification对象,不需要手动创建Notification通知对象。
Notification对象(称为通知)包含name,object和可选dictionary,该对象会随通知调用链传递到的合适selector方法中,即可以在闭包或者selector中使用的时候进行过滤筛选值。
Tips:通知调用链 ——— 观察者先在通知中心注册,然后被观察对象发生变化就制定通知发送规则,根据规则再发送通知到通知中心,通知中心再在注册表中查找对应的已经注册的观察者,然后调用相应观察者的通知响应方法。完成这个过程后会执行post方法下面的语句,除非是异步而且还没发送通知中心正等待中才可以直接执行post下面的语句或者继续合并通知队列,跳过调用相应观察者的响应方法。
selector方法的一般格式:”handleChangeNotification“ 、 “on did something” 、“did do something” 、 “has downloaded something”等格式命名通知方法。
Notification.Name的使用格式: 将通知名称作为静态常量添加到Notification.Name的扩展中更加方便.
extension Notification.Name {
static let didReceiveData = Notification.Name("didReceiveData")
static let didCompleteTask = Notification.Name("didCompleteTask")
static let completedLengthyDownload = Notification.Name("completedLengthyDownload")
}
NotificationQueue.default异/同步发送通知(发送通知方式之一)
在不同线程中notificationQueue都是不同的对象,NotificationQueue可以同/异步合并通知,和通知队列NotificationCenter.default相关联,充当通知中心的缓存区。
通知调用链根据注册通知的方法决定如何在线程中传递,提供观察者响应方法的形参的注册通知方法主要用于主线程处理通知,闭包形式参数的注册通知方法在处理异步发送来通知的时候比较方便。
open func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol
/*
# addObserver方法的形式参数
-queue: 决定接收通知的线程
-nil-与发通知的线程一致,
-currentQueue-与注册通知的线程一致,
-mainQueue-在主线程
-usingBlock: 在规定的线程回调收到的通知
*/
您可以将通知放入NSNotificationQueue中,然后在当前线程中异步的发送通知。调用这个2个方法之后,发布对象会立即调用其线程上的剩余的语句执行。
//返回当前线程的默认通知队列(一般用单例模式default),不同线程的NotificationQueue.default对象都是不同的。
let notificationQueue = NotificationQueue.default
enqueue(_ notification: Notification, postingStyle: NotificationQueue.PostingStyle)
enqueue(_ notification: Notification, postingStyle: NotificationQueue.PostingStyle, coalesceMask: NotificationQueue.NotificationCoalescing, forModes modes: [RunLoop.Mode]?)
NotificationQueue.default之异步合并通知
NotificationQueue都是先进先出的顺序维护通知的,当通知上升到队列的最前面时,队列将其发布到通知中心,通知中心将通知分派给所有注册为观察者的对象。
NotificationQueue 不能无限地合并postingStyle通知, 直到run loop结束或者run loop空闲才能合并通知队列中的通知。队列中的通知在合并后会很快发送到通知中心,还没有到队列中的通知不能合并,但是如果在等待中而且已经在队列中的通知可以合并。由于.now的postingStyle的通知队列互相之间不允许进行任何合并,但是仅可以将前面正在排队的【.whenIdle 和 .asap】通知与其合并成一条通知后发送到NotificationCenter,因为它们仍在队列中等待运行循环空闲。
总而言之,如果您始终将.now用作postingStyle,则NotificationQueue的行为本质上与将通知直接发布到NotificationCenter的行为相同,但是,如果您使用不同的发布方式,则排队的通知可能会合并为一个通知。
let notification = Notification(name: Notification.Name("MyValueChanged"))
NotificationQueue.default.enqueue(notification, postingStyle: .whenIdle, coalesceMask: .none, forModes: nil)
进到coalesceMask形参类型 中会发现是OptionSet类型,所以可以多个组合或单个来指明通知的相似标准。
postingStyle: .now 同步发送通知。.now的同时,为CoalesceMask属性指定.onName,它们将自动合并具有相同名称的所有通知,以防止观察者因重复的通知而超载。
/*
# postingStyle: 形式参数NotificationQueue.PostingStyle.whenIdle 和 .asap , .now。
# coalesceMask: 形式参数合并通知[NotificationQueue.NotificationCoalescing.onName , .onSenderh]和 .none。
## 【.whenIdle 和 .asap】都是异步执行的
NotificationQueue.PostingStyle.whenIdle 也是和coalesceMask参数一起使用,仅在RunLoop处于等待状态时。避免巨大的开销,比如键盘输入字符发送通知。
.PostingStyle.asap 通知发送很多,开销很大的时候使用这个,配合coalesceMask参数NotificationQueue.NotificationCoalescing.onName | .onSender 一起使用,最后将通知合并成一个发送。
.PostingStyle.now 不要求异步发送、用同步发送也可以使用默认的NotificationCenter.default.postNotification发布通知,但是用.now的时候可以通过合并删除相似的通知,因此也要和coalesceMask参数一起使用。
*/
1⃣️Consider the following snippet:
NotificationCenter.default.addObserver(forName: .test, object: nil, queue: nil) { (notification) in
print("Received notification")
}
let queue = NotificationQueue.default
queue.enqueue(Notification(name: .test), postingStyle: .now)
print("Posted")
queue.enqueue(Notification(name: .test), postingStyle: .now)
print("Posted")
queue.enqueue(Notification(name: .test), postingStyle: .now)
print("Posted")
This will print:
Received notification
Posted
Received notification
Posted
Received notification
Posted
Since the postingStyle of .now does not allow for any coaelscing.
2⃣️Now, consider:
let queue = NotificationQueue.default
queue.enqueue(Notification(name: .test), postingStyle: .whenIdle)
print("Posted")
queue.enqueue(Notification(name: .test), postingStyle: .whenIdle)
print("Posted")
queue.enqueue(Notification(name: .test), postingStyle: .whenIdle)
print("Posted")
3⃣️This will print:
Posted
Posted
Posted
Received notification
Since the three notifications can be coalesced before they are delivered when the run loop is idle. You would see the same behaviour with .asap
Finally, consider:
queue.enqueue(Notification(name: .test), postingStyle: .whenIdle)
print("Posted")
queue.enqueue(Notification(name: .test), postingStyle: .whenIdle)
print("Posted")
queue.enqueue(Notification(name: .test), postingStyle: .now)
print("Posted")
This will print:
Posted
Posted
Received notification
Posted
线程替代技术:idle time notification
对于相对短且低优先级的任务,idle time notification 允许你在应用不忙的时候进行任务。Cocoa 通过 NSNotificationQueue 对象提供 idle-time notifications。要请求一个 idle-time notificaiton,使用默认的 NSNotificationQueue 对象发送 NSPostWhenIdle 选项的通知。这个队列会延迟投递 (delivery) 你的通知对象,直到 run loop 对象闲置的时候。
补充
在Cocoa中有很多种两个对象进行通信的途径。当然也能进行直接消息传递。也有像目标-动作,代理,回调这些解耦,一对一的设计模式。KVO允许让很多对象订阅一个事件,但是它把这些对象都联系起来了。另一方面通知让消息全局广播,并且让有监听该广播的对象接收该消息。