原文连接:swift.gg/2018/07/26/…
做者:Ole Begemann
译者:BigNerdCoding
校对:pmst
定稿:CMB
html
简单回答:须要 (在 iOS 11.2 上验证过)ios
几周以前,我在 twitter 上提出了一个问题:git
在 iOS 11 中是否还须要手动移除基于 block 形式的通知观察者?苹果开发文档中比较模糊。
addObserver(forName:object:queue:using:)
中说须要,而removeObserver(_:)
中又代表 iOS 9 以后都不在须要。github
虽然我没有统计准确的数字,可是大体看来持不一样意见的人差很少五五开。express
因此下面咱们就来具体测试验证一下。swift
首先,我所说的基于 block 的接口声明是 NotificationCenter.addObserver(forName: object: queue: using:)
。使用该 API 咱们在通知中心注册了一个函数用于处理对应的通知,而且获得一个表示观察者的返回值。api
class MyObserver {
var observation: Any? = nil
init() {
observation = NotificationCenter.default.addObserver(
forName: myNotification, object: nil, queue: nil) { notification in
print("Received \(notification.name.rawValue)")
}
}
}
复制代码
问题是:当代码中的返回值 observation 销毁时(例如,MyObserver 实例对象析构了),通知中心会不会自动忽略并中止调用处理函数呢?毕竟基于 KeyPath 的 KVO 新接口当观察者销毁后,响应处理再也不被调用,因此通知可能也被理解成是这样进行的。app
或者,咱们依旧须要手动调用 NotificationCenter.removeObserver(_:)
(例如,在 MyObserver 的析构函数 deinit 手动注销)?函数
基于 selector 形式的观察接口 addObserver(_:selector:name:object:)
的手动注销操做在 iOS 9 和 OSX 10.11 以后已经变成可选了。然而在 Foundation 发布注意事项中明确代表 Block 形式的接口依然须要进行手动注销操做。测试
经过
-[NSNotificationCenter addObserverForName:object:queue:usingBlock:_]
形式添加的block类型观察者在无用时依然须要进行注销操做,由于系统会保留对该观察者的强引用。
该文档发布以后是否存在新变化呢?
在 addObserver(forName:object:queue:using:)
文档说明部分也明确指出了注销操做是必要的:
全部经过
addObserver(forName:object:queue:using:)
建立的观察者在析构以前都须要调用removeObserver(_:)
或者removeObserver(_:name:object:)
进行注销操做。
然而 removeObserver(_:)
文档说明处彷佛与之相反:
若是你的 APP 运行在 iOS 9 或者 macOS 10.11 及最新的版本上的话则不须要注销这个观察者在它的析构方法。
该文档中并无对 selector 或者 block 进行区分说明,也就是说该操做同时适用于二者。
经过我写的测试应用,你能够获得验证上诉问题(经过 Xcode 的终端输出)。
下面是我发现的:
removeObserver (_:)
文档存在明显的误导。处理这个问题最好的方式是什么呢?个人建议是:对观察对象进行一次封装。该封装类型的指责就是保持观察者对象而且在析构函数中自动将其注销。
/// Wraps the observer token received from
/// NotificationCenter.addObserver(forName:object:queue:using:)
/// and unregisters it in deinit.
final class NotificationToken: NSObject {
let notificationCenter: NotificationCenter
let token: Any
init(notificationCenter: NotificationCenter = .default, token: Any) {
self.notificationCenter = notificationCenter
self.token = token
}
deinit {
notificationCenter.removeObserver(token)
}
}
复制代码
经过封装处理,咱们将观察者的生命周期和该类型实例进行了绑定。接下来咱们只须要将该封装类型实例经过私有属性进行保存,那么其持有者就会 deinit 触发时销毁该封装实例紧接着销毁观察者实例对象。这样就不须要在代码中对其进行手动注销操做了。另外咱们还能够将该实例声明为 Optional <NotificationToken>
,这样经过将其设置为 nil 也能进行手动注销操做。该模式被称为 资源获取即初始化 (RAII)。
接下来让咱们为 NotificationCenter
编写一个便利点的方法,它为咱们承担了包装观察接口的任务。
extension NotificationCenter {
/// Convenience wrapper for addObserver(forName:object:queue:using:)
/// that returns our custom NotificationToken.
func observe(name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> ())
-> NotificationToken
{
let token = addObserver(forName: name, object: obj, queue: queue, using: block)
return NotificationToken(notificationCenter: self, token: token)
}
}
复制代码
若是此时将原有的 addObserver(forName:object:queue:using:)
替换为新 API ,并将获得 NotificationToken 实例经过属性保存的话,你将再也不须要手动注销操做了。
Chris 和 Florian 也在 Swift Talk episode 27: Typed Notifications 中提到过该技术,我向你强烈的推荐它。