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

如何从SwiftUI Actor中推送状态变更至视图?

从Actor向SwiftUI推送状态变更的成熟方案

方案1:利用AsyncSequence(通用兼容方案)

让Actor暴露一个可订阅的异步序列,SwiftUI视图通过.task修饰符订阅该序列,在主线程更新UI状态。

修改你的RandomTicker Actor,添加异步序列推送事件:

actor RandomTicker {
    private let tickStream = AsyncThrowingStream<Int, Error> { continuation in
        Task { [weak self] in
            guard let self = self else { return }
            while !Task.isCancelled {
                try await Task.sleep(for: .seconds(Int.random(in: 1...4)))
                let randomValue = Int.random(in: 0...100)
                continuation.yield(randomValue)
            }
            continuation.finish()
        }
    }
    
    var ticks: AsyncThrowingStream<Int, Error> {
        tickStream
    }
}

在SwiftUI视图中订阅序列并更新状态:

struct TickerView: View {
    @State private var currentValue = 0
    private let ticker = RandomTicker()
    
    var body: some View {
        Text("当前值:\(currentValue)")
            .task {
                do {
                    for await value in ticker.ticks {
                        await MainActor.run {
                            currentValue = value
                        }
                    }
                } catch {
                    print("订阅终止:\(error)")
                }
            }
    }
}

方案2:使用Observation框架(iOS17+/macOS14+)

借助SwiftUI的Observation框架,让Actor持有可观察的状态对象,Actor更新状态时,SwiftUI会自动感知并刷新视图。

首先定义可观察状态类:

@Observable
class TickerState {
    var currentValue = 0
}

修改RandomTicker Actor持有状态对象并更新:

actor RandomTicker {
    private let state: TickerState
    
    init(state: TickerState) {
        self.state = state
    }
    
    func start() async {
        while true {
            try! await Task.sleep(for: .seconds(Int.random(in: 1...4)))
            let randomValue = Int.random(in: 0...100)
            state.currentValue = randomValue
        }
    }
}

在SwiftUI视图中使用状态对象:

struct TickerView: View {
    private let tickerState = TickerState()
    private let ticker: RandomTicker
    
    init() {
        ticker = RandomTicker(state: tickerState)
    }
    
    var body: some View {
        Text("当前值:\(tickerState.currentValue)")
            .task {
                await ticker.start()
            }
    }
}

方案3:Combine + PassthroughSubject(iOS13+)

针对更早系统版本,可使用Combine框架,让Actor通过PassthroughSubject发送事件,视图订阅后更新状态。

修改RandomTicker Actor:

import Combine

actor RandomTicker {
    private let subject = PassthroughSubject<Int, Never>()
    var tickPublisher: AnyPublisher<Int, Never> {
        subject.eraseToAnyPublisher()
    }
    
    func start() async {
        while true {
            try! await Task.sleep(for: .seconds(Int.random(in: 1...4)))
            let randomValue = Int.random(in: 0...100)
            await MainActor.run {
                subject.send(randomValue)
            }
        }
    }
}

在SwiftUI视图中订阅:

import Combine

struct TickerView: View {
    @State private var currentValue = 0
    private let ticker = RandomTicker()
    private var cancellables = Set<AnyCancellable>()
    
    var body: some View {
        Text("当前值:\(currentValue)")
            .onAppear {
                ticker.tickPublisher
                    .receive(on: DispatchQueue.main)
                    .sink { [weak self] value in
                        self?.currentValue = value
                    }
                    .store(in: &cancellables)
                
                Task {
                    await ticker.start()
                }
            }
    }
}

注意事项

  • 所有UI状态更新必须在主线程执行,SwiftUI状态变量仅允许主线程修改。
  • 使用AsyncSequence时,.task修饰符会在视图消失时自动取消任务;使用Combine需手动管理cancellables避免内存泄漏。
  • Observation框架下,状态对象的修改会自动触发视图刷新,无需手动发送通知,代码更简洁。

内容的提问来源于stack exchange,提问作者Curious Jorge

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 08:40:14