关于 Notification 的使用

0x00 前言

一直以来,对于 Notification 的使用都理解得不是特别透彻,dealloc 中要不要移除 observerblock 中要不要加 weak,一直都是能加就加,根本未考虑过为什么要加或者说为什么可以不加。这篇记录就将这些问题一次性地弄个明白。

0x01 removeObserver?

Apple 的官方文档说明中,在 iOS 9.0,macOS 10.11 及以后的系统版本中,使用 addObserver:selector:name:object: 方式添加的通知可以不再手动移除通知。而使用 addObserverForName:object:queue:usingBlock: 方式添加的通知在之前是需要手动移除的,而在新版本中则没有特意指明是否需要移除。

那么我们就用代码来实际测试一下。

首先,我们为 NotificationCenter 添加一个扩展方法,用自定义的方法对 removeObserver 进行交换,并在自定义方法中输出我们想要的信息。这样就能够在退出界面时在控制台方便地看到 Notification 到底有没有移除 observer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extension NotificationCenter {
class func swizzling() {
let selector = #selector(NotificationCenter.removeObserver(_:))

let method = class_getInstanceMethod(self, selector)!
let newmethod = class_getInstanceMethod(self, #selector(my_removeObserver(_:)))

method_exchangeImplementations(method, newmethod!)
}

@objc func my_removeObserver(_ noti: Any?) {
self.my_removeObserver(noti)
print("Observer has been removed")
}
}

接下来我们创建三个界面,分别为 FirstControllerSecondControllerThirdController

FirstControllerviewDidLoad 方法中调用 NotificationCenter.swizzling()

SecondController 中添加接收通知的代码:

1
2
3
4
5
6
7
NotificationCenter.default.addObserver(self, selector: #selector(notificationReceived), name: .init("TestNotification"), object: nil)

...

@objc private func notificationReceived(_ noti: Notification) {
print("Has received notifciation by selector.")
}

然后在 ThirdControllerviewDidLoad 方法中发送通知:

1
NotificationCenter.default.post(name: .init(rawValue: "TestNotification"), object: nil)

此时便已万事俱备了,接下来开始我们的测试。

运行我们的 demo app,首先进入 FirstController,然后 present 出 SecondController,在第二个界面再次 present 出 ThirdController

此时, ThirdController 中的通知消息已经发出,控制台也能看到输出了 “Has received notifciation by selector.”,此时将 ThirdController 关闭,控制台再次打印出 “Observer has been removed”。这说明 ThirdController 关闭的时候自动调用了 removeObserver 方法。
再将 SecondController 关闭,控制台同样打印出 “Observer has been removed”,这说明的确不用再手动移除通知的 observer 了。

0x02 通知的 block 形式与 weak

SecondController 中的接收通知代码替换为 block 形式:

1
2
3
4
5
6
7
8
9
NotificationCenter.default.addObserver(forName: .init("TestNotification"), object: nil, queue: .main) { (noti) in
self.notificationCallback(noti)
}

...

private func notificationCallback(_ noti: Notification) {
print("Has received notification by block.")
}

再以相同的流程跑一遍,可以发现关闭 SecondController 并没有打印 “Observer has been removed”,是因为 block 形式的通知不支持自动移除吗?我们在 deinit 中手动移除一下再来看看。

1
2
3
4
deinit {
print("Second view controller deinited.")
NotificationCenter.default.removeObserver(self)
}

再试一下,可以发现 deinit 方法根本没有调用,所以,根本原因其实是产生了循环引用导致不能正常释放。因此在 block 中添加weak,修改后的代码为:

1
2
3
4
5
6
7
8
9
NotificationCenter.default.addObserver(forName: .init("TestNotification"), object: nil, queue: .main) { [weak self] (noti) in
self?.notificationCallback(noti)
}

...

deinit {
print("Second view controller deinited.")
}

再运行一遍我们的demo,可以看到控制台输出了 “Observer has been removed” 和 “Second view controller deinited.” 这两句话,证明了 observer 已正常移除,SecondController 也已正常释放。

0x03 总结

通过以上的测试,可以得出结论,在iOS 9.0,macOS 10.11 及以后的版本中,NotificationCenterobserver 不再需要手动移除,使用 block 形式的通知时,需要加上 weak 以避免循环引用的出现。