Reputation: 57203
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))
}
}
}
Upvotes: 1
Views: 850
Reputation: 438467
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))
}
}
}
Upvotes: 1