考虑这个简单的类:
import Foundation
class ExampleClass {
init() {
let notificationCenter = NotificationCenter.default
var observer: NSObjectProtocol? = nil
// A warning is emitted for the next line
observer = notificationCenter.addObserver(
forName: .NSExtensionHostDidEnterBackground,
object: nil,
queue: nil
) { [weak self] _ in
self?.doSomething()
notificationCenter.removeObserver(observer!)
}
}
func doSomething() {
print("We got the notification")
}
}
这段代码使用了苹果在其NotificationCenter.addObserver(forName:object:queue:using:)
文档中建议的确切模式,其中NotificationCenter
为我们提供了一些符合NSObjectProtocol
的不透明令牌,我们稍后使用该令牌来删除观察者。
不过,最近,此代码开始产生警告。在分配observer
的行上,编译器抱怨
"观察者"在可发送闭包捕获后发生突变
我理解编译器的来源:如果observer
是一个值类型,那么闭包确实会得到它的"旧"版本。不过,这段代码确实有效,这向我表明,addObserver()
要么返回引用类型,要么返回NotificationCenter
存储的数据的值类型句柄。(不幸的是,苹果没有给我们一个更具体的方法返回类型。(
在这种情况下,此警告是否表明存在实际问题?如果是的话,最好的替代模式是什么?
您可以将观察者存储在对象本身上:
import Foundation
class ExampleClass {
private var observer: NSObjectProtocol?
init() {
let notificationCenter = NotificationCenter.default
observer = notificationCenter.addObserver(
forName: .NSExtensionHostDidEnterBackground,
object: nil,
queue: nil
) { [weak self] _ in
if let self {
self.doSomething()
notificationCenter.removeObserver(self.observer!)
}
}
}
func doSomething() {
print("We got the notification")
}
}
这种解决方法会使警告静音,并且不应该以有意义的方式更改程序的语义。它有点不那么优雅,因为一个本可以保留在init
(及其闭包(本地的变量现在暴露给整个类,但它应该具有与前一版本代码相同的效果。
一个现代的替代方案是Combine
发行商
import Combine
class ExampleClass {
private var subscription : AnyCancellable?
init() {
subscription = NotificationCenter.default
.publisher(for: .NSExtensionHostDidEnterBackground)
.sink { [weak self] _ in
self?.doSomething()
}
}
func doSomething() {
print("We got the notification")
subscription?.cancel()
subscription = nil
}
}