为什么可以从与Swift中原始线程不同的线程中删除观察者?
我认为我们可能需要深入研究Swift源代码,但是我对此感到很好奇。这是一个示例代码来演示:
class ViewController: UIViewController {
var counter = 0
@objc dynamic var testValue: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true, block: { [weak self] (timer) in
guard let self = self else {
print("return")
return
}
self.testValue = !self.testValue
})
addObserver(self, forKeyPath: "testValue", options: .new, context: nil)
Timer.scheduledTimer(withTimeInterval: 7.0, repeats: false, block: { (timer) in
DispatchQueue.global(qos: .background).async {
self.removeObserver(self, forKeyPath: "testValue")
}
})
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "testValue" {
print("counter : \(counter)")
counter += 1
}
}
}
从另一个线程中删除观察者没有任何技术问题,但我建议不要这样做。最令人担忧的是观察者的重新分配与其从KVO注销之间的竞争状况。正如the documentation对removeObserver(_:forKeyPath:)
所说:
在释放
removeObserver(_:forKeyPath:context:)
中指定的任何对象之前,请确保调用此方法(或addObserver(_:forKeyPath:options:context:)
)。>>现在,在您的情况下,您的两个计时器都恰好保持着对观察者的强烈引用(这本身就是一个问题;您应该在计时器中使用
[weak self]
模式,并在必要时使它们无效),但是如果您已修复,那,您现在要介绍一场比赛。
如果在init
中添加观察者,然后在deinit
中将其删除,则可以消除任何竞争条件。 (此外,如果您使用modern KVO syntax,它也会消除这种取消分配/取消注册的情况。)
也请记住,在更新observeValue(forKeyPath:of:change:context:)
属性的线程上调用了testValue
。因此,如果您担心线程问题,也必须同时考虑testValue
和counter
的线程安全性。
在没有任何同步的情况下(例如,即使您正在考虑从后台线程中删除观察者,您可能会假设testValue
不会从后台线程中进行更新),我建议假设继承为observeValue
明确。因此,假设您像这样添加观察者:
addObserver(self, forKeyPath: #keyPath(testValue), options: .new, context: &observerContext)
然后您可以添加
dispatchPrecondition
以使假设明确:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { guard context == &observerContext else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } if keyPath == #keyPath(testValue) { dispatchPrecondition(condition: .onQueue(.main)) // given that `counter` is not synchronized, let’s warn developer if ever updated `testValue` on background thread print("counter: \(counter)") counter += 1 } }
显然,如果通过某种同步使
counter
成为线程安全的,则不需要此先决条件。
[此外,我们显然不会使用KVO来观察当前类的属性(我们通常只使用Swift观察器),但是我假设您出于说明目的简化了此操作。