如何以线程安全的方式捕获 属性

how do I capture a property in thread-safe way

我的 class A 有一个 属性 的 class B 可以重置:

class A1 {
    private var b = B(0)

    func changeB(i : Int) {
        b = B(i)
    }

    func testB(k : Int) -> Bool {
        return b.test(k)
    }
}

class B {
    private let b : Int;

    init(_ i : Int) {
        b = i
    }

    func test(_ k : Int) -> Bool {
        return b == k
    }
}

到目前为止一切顺利。如果我想在多线程场景下使用 class A ,我必须添加一些同步机制,因为 Swift are not atomic 中的属性本身:

class A2 {
    private var b = B(0)
    private let lock = NSLock()

    func changeB(i : Int) {
        lock.lock()
        defer { lock.unlock() }
        b = B(i)
    }
    
    func testB(k : Int) -> Bool {
        lock.lock()
        defer { lock.unlock() }
        return b.test(k)
    }

}

但是现在我想引入一个闭包:

class A3 {
    func listenToB() {
        NotificationCenter.default.addObserver(forName: Notification.Name("B"), object: nil, queue: nil) { 
        [b] (notification) in
            let k = notification.userInfo!["k"] as! Int
            print(b.test(k))
        }
    }
}

我的理解是否正确,这不是线程安全的?如果我也捕获 lock,这会得到修复吗,如下所示?

class A4 {
    func listenToB() {
        NotificationCenter.default.addObserver(forName: Notification.Name("B"), object: nil, queue: nil) { 
        [lock, b] (notification) in
            let k = notification.userInfo!["k"] as! Int
            lock.lock()
            defer { lock.unlock() }
            print(b.test(k))
        }
    }
}

是的,使用捕获的 lock 确保观察者的闭包与使用相同锁的其他任务同步。您可以使用此捕获模式,因为 lock 恰好是一个常量。

这引发了更基本的问题,即捕获 b 引用,它不是常量。这意味着如果您在某个中间时间点调用 changeB,您的通知块仍将引用原始捕获的 B,而不是新的。

所以,如果你想让它引用当前的 B:

,你真的想回到 weak self 模式
class A {
    func listenToB() {
        NotificationCenter.default.addObserver(forName: Notification.Name("B"), object: nil, queue: nil) { [weak self] notification in
            guard let self = self else { return }

            let k = notification.userInfo!["k"] as! Int
            self.lock.lock()
            defer { self.lock.unlock() }
            print(self.b.test(k))
        }
    }
}