说起 iOS 中的 Notification,大家肯定不屑一顾,能不会用吗?甚至有些初学者出现了乱用的情况,无论什么情况需要传值,就来个Notification。但是,大家真的用对了吗?
缘起
这个问题是一个朋友小A(自身领域很牛)最近被他们公司逼迫去临时写 iOS,他表示不理解 NSNotificationCenter 的一个问题,所以就有了这个文章。考虑到可能很多人(尤其是初学者)会有同样的疑问,我在下面整理了我们的对话。
对话
(A:小A,M:我):
A:为什么我通过 NSNotificationCenter 发送一个消息之后,程序会停在那里,要等待其他N多地方处理完才继续执行?是我写的有问题吗?
M:嗯,这样是正常的,只要你没有手动切换线程,接收端的代码会在发送端的线程里执行。
A:好奇怪,为什么不是异步执行?按理说我发个消息,我继续做我的事,收到消息的做他的事情,这不皆大欢喜?
M:因为 NSNotificationCenter 的实现就是如此,它仅仅相当于是把其他地方的代码拷贝在这里执行,用伪代码表示就是 for block in blocks : doBlock(block). 本质上,它是为了处理架构的问题,只是提供一种观察者模式的实现,为了代码结构的清晰,而不是异步的问题。 至于为什么这样实现,虽然有点违背常理,但是很多时候,其实我并不希望异步执行,我就是希望先让其他代码处理消息,然后才可以继续。所以默认异步执行并不一定就是很好的选择。
A:明白了。不过我看到 block 的方式,有加到 queue 的方式诶,但是我试验过了,还是同步执行的,为什么?即:
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[[NSNotificationCenter defaultCenter] addObserverForName: ANOTI object:nil queue:backgroundQueue usingBlock:^(NSNotification * _Nonnull note) {
// do something
}];
M:这里的 Queue 是针对接收消息的 block 而言的,而发送消息的地方还是会等在那里。这是因为在 NotifactionCenter 的实现里会调用一个 waitUntilAllOperationsAreFinished 这个函数,所以会等待所有消息处理完才会继续。
A:好吧,那我只能手动切换线程了。
M:也不一定...你其实可以使用 NSNotifictionQueue。它是专门用来做异步发送消息的,而且由于是异步发送消息,所以提供合并通知的功能,比如多个相同的通知,那么我们只需要处理一次即可。
A:哇,这个正好适合我的需求。终于可以让 Notification 加持多线程了!
M:呃...其实,NSNotifictionQueue 并没有多开线程喔!
A:啊?那是怎么做到异步的呢?
M:它用的是iOS中的 Runloop 机制,你暂时不用管,你只需要知道它是iOS底层为每个线程创建的一个机制,当有事情的时候,执行事情,没事的时候就进行休眠。而 NSNotifictionQueue 就将这次时候做完后就处理 Notifaction (NSPostASAP),或者是等 Runloop 出于空闲状态 (NSPostWhenIdle)。另外终于如果选择了 NSPostNow,那么与普通发送消息一模一样,仅仅是可以合并消息(与之前的异步消息合并)。
A:好吧,看来还挺复杂的,我去翻翻文档学习一下。
总结
所以,NotificationCenter 发送的消息基本都是同步的,虽然用 block 的方式可以实现并行处理消息,但仍然发送/执行消息的过程仍然是同步的。(同步/异步,串行/并行,希望能够分清楚。)。而想实现异步,一种方法是自己做线程切换,而更好的方法是利用苹果提供的 NSNotifictionQueue。具体用法不多讲,可参考苹果官方文档。