You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:14:52