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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 16:36:22