如何在Swift中跨不同上下文实现线程安全的属性包装器通知?
实现跨上下文的共享状态属性包装器(带上下文感知的通知)
要解决你遇到的问题,核心是在添加观察者时捕获其所在的执行上下文(Actor/线程队列),并让回调始终在该上下文执行。下面是一套完整的实现方案,覆盖SwiftUI视图、@Observable类、Actor等所有场景。
1. 基础工具:线程安全的Mutex
先实现一个符合Sendable的线程安全锁,确保状态读写的原子性:
import Foundation final class Mutex<Value>: @unchecked Sendable { private let lock = NSLock() private var value: Value init(_ value: Value) { self.value = value } func withLock<Result>(_ operation: (inout Value) throws -> Result) rethrows -> Result { lock.lock() defer { lock.unlock() } return try operation(&value) } func get() -> Value { withLock { $0 } } }
2. 共享状态容器:MemoryShared
这个容器负责存储全局状态、管理观察者,并在状态变化时触发通知。关键是存储绑定了执行上下文的回调,确保通知在观察者创建时的上下文执行:
final class MemoryShared<Value>: Sendable where Value: Sendable { private let state: Mutex<Value> // 存储不同上下文的观察者 private let observers = Mutex<[ObserverContext]>([]) init(_ initialValue: Value) { self.state = Mutex(initialValue) } // 修改状态并触发通知 func withLock(_ action: (inout Value) -> Void) { state.withLock(action) notifyObservers() } func get() -> Value { state.get() } // 添加观察者:自动捕获当前执行上下文 func addObserver(callback: @Sendable @escaping () -> Void) { if let currentActor = Task.currentActor { // 当前在Actor上下文,绑定Actor执行回调 observers.withLock { $0.append(.actor(currentActor, callback)) } } else { // 当前在普通线程,绑定当前队列执行回调 let currentQueue = DispatchQueue.current ?? .global() observers.withLock { $0.append(.queue(currentQueue, callback)) } } } // 移除观察者(避免内存泄漏,可选) func removeObserver(callback: @Sendable () -> Void) { observers.withLock { $0.removeAll { switch $0 { case .actor(_, let cb): return cb === callback case .queue(_, let cb): return cb === callback } } } } // 通知所有观察者 private func notifyObservers() { let currentObservers = observers.get() for observer in currentObservers { // 检查当前是否和观察者在同一Actor上下文,是的话同步执行 if let currentActor = Task.currentActor, case .actor(let observerActor, let callback) = observer, currentActor === observerActor { callback() continue } // 不同上下文,异步切换到目标上下文执行 observer.execute() } } } // 定义观察者上下文类型,支持Actor和普通队列 private enum ObserverContext: Sendable { case actor(any Actor, @Sendable () -> Void) case queue(DispatchQueue, @Sendable () -> Void) func execute() { switch self { case .actor(let actor, let callback): Task(isolated: actor) { callback() } case .queue(let queue, let callback): queue.async { callback() } } } } // 扩展DispatchQueue,获取当前执行队列 private extension DispatchQueue { static var current: DispatchQueue? { let key = DispatchSpecificKey<DispatchQueue>() defer { DispatchQueue.current?.setSpecific(key: key, value: nil) } let queue = DispatchQueue.global() queue.setSpecific(key: key, value: queue) DispatchQueue.main.async {} return DispatchQueue.getSpecific(key: key) } }
3. 属性包装器:SharedState
这个包装器适配不同场景,自动处理视图刷新、Observable对象更新等逻辑:
import SwiftUI @propertyWrapper struct SharedState<Value>: DynamicProperty where Value: Sendable & Equatable { private let shared: MemoryShared<Value> @State private var localValue: Value // SwiftUI视图初始化:自动绑定视图刷新 init(_ shared: MemoryShared<Value>) { self.shared = shared self._localValue = State(initialValue: shared.get()) shared.addObserver { [weak self] in guard let self else { return } let newValue = self.shared.get() if self.localValue != newValue { self.localValue = newValue } } } // Observable类/Actor初始化:触发属性变化通知 init(shared: MemoryShared<Value>) { self.shared = shared self._localValue = State(initialValue: shared.get()) shared.addObserver { [weak self] in guard let self else { return } let newValue = self.shared.get() if self.localValue != newValue { self.localValue = newValue } } } var wrappedValue: Value { get { localValue } nonmutating set { shared.withLock { $0 = newValue } } } }
4. 使用示例
全局共享状态
static let globalCount = MemoryShared<Int>(0)
SwiftUI视图
@MainActor struct MyView: View { @SharedState(globalCount) var count: Int var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } } } }
@Observable类
@Observable class MyObservable { @SharedState(shared: globalCount) var count: Int func increment() { count += 1 } }
Actor
actor MyBackgroundActor { @SharedState(shared: globalCount) var count: Int func logCount() async { print("Actor count: \(count)") } }
几点说明
- 上下文自动绑定:添加观察者时会自动捕获当前的Actor/队列,回调始终在该上下文执行,无需手动切换线程。
- 线程安全:所有状态操作通过
Mutex保证原子性,通知执行由Swift自动处理上下文切换,避免竞态问题。 - 内存泄漏防护:使用
[weak self]避免循环引用,可通过removeObserver方法在对象销毁时清理回调。 - SwiftUI适配:通过
DynamicProperty和@State自动触发视图刷新,符合SwiftUI的更新机制。
内容的提问来源于stack exchange,提问作者Jordan Ellis
相关产品推荐
相关产品推荐

