My class A has a property of class B which can be reset:
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
}
}
So far so good. If I want to use class A in multithreading scenario, I must add some synchronization mechanism, because properties in Swift are not atomic by themselves:
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)
}
}
But now I want to introduce a closure:
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))
}
}
}
Do I understand correctly that this is not thread-safe? Will this get fixed if I capture lock
as well, as below?
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))
}
}
}
Yes, using the captured lock
ensures that the observer’s closure is synchronized with other tasks using the same lock. You can use this capturing pattern because lock
happens to be a constant.
That raises the more fundamental problem, namely the capturing of the b
reference, which is not constant. That means that if you call changeB
at some intervening point in time, your notification block will still reference the original captured B
, not the new one.
So, you really want to fall back to the weak self
pattern if you want this to reference the current B
:
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))
}
}
}