NotificationCenter观察器"在可发送闭包捕获后发生突变"



考虑这个简单的类:

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
}
}

最新更新