Swift UI结构体属性无法从调度队列更新问题求助
问题描述
我刚通过实战项目开始学习Swift/UI。现有两个类/结构体:
import SwiftUI struct WordList: View { @State var current: Int? = nil init() { SpeechPlayer.shared.subscribeOnCurrentUpdated(self.onCurrentUpdated) } public func onCurrentUpdated(_ current: Int?) -> Void { self.current = current print(self.current, current) // <-- current = 1,但self.current仍为nil } var body: some View { ...
import Foundation class SpeechPlayer { static let shared = SpeechPlayer() private var current: Int = 0 private var onCurrentUpdateSubscribers: [(_ current: Int?) -> Void] = [] private init() { DispatchQueue.global().async { while true { self.current += 1 DispatchQueue.global().async { self.onCurrentUpdateSubscribers.forEach { callback in callback(self.current) } } sleep(1) } } } public func subscribeOnCurrentUpdated(_ callback: @escaping (_ current: Int?) -> Void) -> Void { self.onCurrentUpdateSubscribers.append(callback) } }
问题在于调用onCurrentUpdated函数时,参数current为有效数字,但self.current并未更新,始终为nil。
解决方案
1. 必须在主线程更新@State变量
SwiftUI的@State属性仅允许在主线程修改,你的SpeechPlayer在后台队列中触发回调,直接修改self.current不会触发UI状态更新,甚至会导致不可预期的问题。
修改onCurrentUpdated方法,切换到主线程更新状态:
public func onCurrentUpdated(_ current: Int?) -> Void { DispatchQueue.main.async { self.current = current print(self.current, current) } }
2. 解决结构体值类型的捕获问题
WordList是结构体(值类型),在init中订阅回调时,捕获的是当前结构体实例的副本。当SwiftUI因状态变化重新渲染WordList时,会创建新的结构体实例,旧实例的回调无法影响新实例的@State。
推荐使用@ObservableObject/@StateObject来管理状态,替代结构体内部的@State:
步骤1:创建ViewModel类管理状态
import SwiftUI import Combine class WordListViewModel: ObservableObject { @Published var current: Int? = nil init() { SpeechPlayer.shared.subscribeOnCurrentUpdated { [weak self] current in DispatchQueue.main.async { self?.current = current } } } }
步骤2:在View中持有ViewModel
struct WordList: View { @StateObject private var viewModel = WordListViewModel() var body: some View { // 使用viewModel.current展示数据 Text(viewModel.current?.description ?? "nil") } }
3. 优化SpeechPlayer的线程逻辑
可以简化SpeechPlayer内部的队列嵌套,直接在主线程触发回调,减少上层代码的线程切换操作:
private init() { DispatchQueue.global().async { while true { self.current += 1 // 直接在主线程通知订阅者 DispatchQueue.main.async { self.onCurrentUpdateSubscribers.forEach { callback in callback(self.current) } } sleep(1) } } }
内容的提问来源于stack exchange,提问作者Denis Smagin
相关产品推荐
相关产品推荐

