如何以线程安全的方式捕获 属性
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))
}
}
}
我的 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))
}
}
}