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

SwiftUI中DispatchQueue.main.async赋值sharedState.userDTO崩溃问题

问题:SwiftUI中DispatchQueue.main.async内赋值共享状态导致崩溃

问题现象

执行以下代码时应用崩溃,注释sharedState.userDTO = ret则运行正常:

DispatchQueue.main.async {
    sharedState.userDTO = ret
}

背景信息

  • sharedState是单例模式的ObservableObject,在主应用入口创建并通过environmentObject注入视图
  • userDTO是sharedState的@Published属性
  • 原意图是通过DispatchQueue.main.async确保UI状态在主线程更新
  • 已确认:sharedState与userDTO初始化正常、无并发操作修改该属性、ret不为nil

完整相关代码

MyText视图代码

//
//  CustomTextField.swift
//  DataMapper
//
//  Created by János Kukoda on 2023. 07. 01..
//

import SwiftUI
struct MyText: View {
    let text: String
    let style: String
    let canEdit: Bool
    let expressionAB: IdentifiableText
    let keyword: Keyword
    
    @State private var isEditing = false
    @State private var editText = ""
    @State private var isContentChanged: Bool = false
    
    @EnvironmentObject var sharedState: SharedState
    
    var body: some View {
        HStack(spacing: 4) {
            if style == "constant" {
                TextField("", text: $editText)
                    .frame(width: 200)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding([.horizontal, .vertical], 4)
                    .fixedSize(horizontal: false, vertical: true)
                    .onAppear {
                        editText = text
                    }
                    .onChange(of: editText) { newValue in
                        isContentChanged = true
                        Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
                            if !isContentChanged {
                                if let ruleInd = sharedState.ruleInd,
                                   let userDTO = sharedState.userDTO,
                                   let teamId = sharedState.teamId,
                                   let transformationId = sharedState.transformationId,
                                   let subTransformationInd = sharedState.subTransformationInd
                                {
                                    print("New value entered 3: \(editText)")
                                    sendToBackend(value: editText, keyPath: ["teams", teamId, "transformations", transformationId, "subTransformations", subTransformationInd, "outputs", 0, "mapRules", ruleInd, "objectrule"] + keyword.keyPath, operation: "setValue")
                                    print(editText)
                                    let newValue = editText
                                    var ret = updateClient(userDTO: userDTO, value: editText, keyPath: ["teams", teamId, "transformations", transformationId, "subTransformations", subTransformationInd, "outputs", 0, "mapRules", ruleInd, "objectrule"] + keyword.keyPath, operation: "setValue")
                                    if let ret = ret {
                                        DispatchQueue.main.async {
                                            sharedState.userDTO = ret
                                        }
                                    }
                                }
                            }
                            isContentChanged = false
                        }
                    }
            } else {
                Button(action: {
                    print(expressionAB.keyPath)
                    sharedState.ruleKeyPathSegment = expressionAB.keyPath
                    sharedState.expression = expressionAB.expression
                    sharedState.expInd = expressionAB.rowNum
                }) {
                    if style == "keyword" {
                        Text(text).modifier(KeywordStyle())
                    } else if style == "variable" {
                        Text(text).modifier(VariableStyle())
                    } else if style == "constant" {
                        Text(text).modifier(ConstantStyle())
                    } else {
                        Text(text)
                    }
                }
            }
        }
    }
}

主应用入口代码

import SwiftUI

@main
struct DataMapperApp: App {
    var sharedState = SharedState()
    
    init() {
        UITraitCollection.current = UITraitCollection(userInterfaceStyle: .dark)
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(sharedState).colorScheme(.dark)
        }
    }
}

SharedState声明

class SharedState: ObservableObject {
    @Published var teamId: String? = "responsive"
    @Published var transformationId: String?
    @Published var typeTreeId: String?
    @Published var selectedMenuItem: ...
    // userDTO属性声明(推测为@Published)
    @Published var userDTO: UserDTO?
}

调试与解决思路

调试步骤

  1. 查看崩溃日志:优先查看Xcode崩溃报告的调用栈,确认崩溃类型(如EXC_BAD_ACCESS、KVO异常、线程冲突等),定位具体崩溃点
  2. 验证线程环境:Timer的scheduledTimer默认在主线程执行,可在Timer回调内添加assert(Thread.isMainThread)确认,当前代码的DispatchQueue.main.async属于多余嵌套
  3. 检查SharedState线程安全:ObservableObject的@Published属性必须在主线程修改,确认updateClient方法是否在后台线程执行,返回的ret是否存在线程不安全状态
  4. 检查视图生命周期:确认MyText视图是否在Timer回调执行前已被销毁,导致@EnvironmentObject的sharedState失效,可在赋值前添加assert(sharedState != nil)
  5. 排查ret的有效性:确认updateClient返回的ret对象内存有效,无未初始化属性或野指针

解决建议

  1. 移除多余的主线程嵌套:直接在Timer回调内赋值,无需额外的DispatchQueue.main.async
    修改代码:
    if let ret = ret {
        sharedState.userDTO = ret
    }
    
  2. 强制SharedState主线程修改:给SharedState添加主线程检查,避免意外的后台线程修改
    class SharedState: ObservableObject {
        private func ensureMainThread() {
            assert(Thread.isMainThread, "SharedState属性必须在主线程修改")
        }
        
        @Published var userDTO: UserDTO? {
            willSet {
                ensureMainThread()
            }
        }
        
        // 其他@Published属性同理添加检查
    }
    
  3. 管理Timer生命周期:避免视图销毁后Timer仍执行,添加Timer引用并在视图消失时取消
    struct MyText: View {
        // ... 现有属性
        @State private var debounceTimer: Timer?
        
        var body: some View {
            HStack(spacing: 4) {
                if style == "constant" {
                    TextField("", text: $editText)
                        // ... 现有修饰符
                        .onChange(of: editText) { newValue in
                            isContentChanged = true
                            // 先取消之前的Timer
                            debounceTimer?.invalidate()
                            debounceTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
                                // 原逻辑
                            }
                        }
                        .onDisappear {
                            debounceTimer?.invalidate()
                        }
                }
                // ... 其他视图逻辑
            }
        }
    }
    
  4. 检查updateClient方法:确认该方法返回的ret对象是完整初始化的,内部无线程不安全操作

内容的提问来源于stack exchange,提问作者János

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 10:12:32