为什么可以从不同于Swift中原始线程的线程中删除观察者?

问题描述 投票:0回答:1

为什么可以从与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
        }
    }
}
ios swift observer-pattern key-value-observing observers
1个回答
0
投票

从另一个线程中删除观察者没有任何技术问题,但我建议不要这样做。最令人担忧的是观察者的重新分配与其从KVO注销之间的竞争状况。正如the documentationremoveObserver(_:forKeyPath:)所说:

在释放removeObserver(_:forKeyPath:context:)中指定的任何对象之前,请确保调用此方法(或addObserver(_:forKeyPath:options:context:))。>>

现在,在您的情况下,您的两个计时器都恰好保持着对观察者的强烈引用(这本身就是一个问题;您应该在计时器中使用[weak self]模式,并在必要时使它们无效),但是如果您已修复,那,您现在要介绍一场比赛。

如果在init中添加观察者,然后在deinit中将其删除,则可以消除任何竞争条件。 (此外,如果您使用modern KVO syntax,它也会消除这种取消分配/取消注册的情况。)


也请记住,在更新observeValue(forKeyPath:of:change:context:)属性的线程上调用了testValue。因此,如果您担心线程问题,也必须同时考虑testValuecounter的线程安全性。

在没有任何同步的情况下(例如,即使您正在考虑从后台线程中删除观察者,您可能会假设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观察器),但是我假设您出于说明目的简化了此操作。

© www.soinside.com 2019 - 2024. All rights reserved.