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? }
调试与解决思路
调试步骤
- 查看崩溃日志:优先查看Xcode崩溃报告的调用栈,确认崩溃类型(如
EXC_BAD_ACCESS、KVO异常、线程冲突等),定位具体崩溃点 - 验证线程环境:Timer的
scheduledTimer默认在主线程执行,可在Timer回调内添加assert(Thread.isMainThread)确认,当前代码的DispatchQueue.main.async属于多余嵌套 - 检查SharedState线程安全:
ObservableObject的@Published属性必须在主线程修改,确认updateClient方法是否在后台线程执行,返回的ret是否存在线程不安全状态 - 检查视图生命周期:确认MyText视图是否在Timer回调执行前已被销毁,导致
@EnvironmentObject的sharedState失效,可在赋值前添加assert(sharedState != nil) - 排查ret的有效性:确认
updateClient返回的ret对象内存有效,无未初始化属性或野指针
解决建议
- 移除多余的主线程嵌套:直接在Timer回调内赋值,无需额外的
DispatchQueue.main.async
修改代码:if let ret = ret { sharedState.userDTO = ret } - 强制SharedState主线程修改:给SharedState添加主线程检查,避免意外的后台线程修改
class SharedState: ObservableObject { private func ensureMainThread() { assert(Thread.isMainThread, "SharedState属性必须在主线程修改") } @Published var userDTO: UserDTO? { willSet { ensureMainThread() } } // 其他@Published属性同理添加检查 } - 管理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() } } // ... 其他视图逻辑 } } } - 检查updateClient方法:确认该方法返回的
ret对象是完整初始化的,内部无线程不安全操作
内容的提问来源于stack exchange,提问作者János
相关产品推荐
相关产品推荐

