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
lockensures that the observer’s closure is synchronized with other tasks using the same lock. You can use this capturing pattern becauselockhappens to be a constant.That raises the more fundamental problem, namely the capturing of the
breference, which is not constant. That means that if you callchangeBat some intervening point in time, your notification block will still reference the original capturedB, not the new one.So, you really want to fall back to the
weak selfpattern if you want this to reference the currentB: