SwiftUI异步函数完成后返回上一页崩溃问题排查
问题描述
我有一个SwiftUI视图代码如下:
@Environment(\.presentationMode) var presentationMode ... Button { Task { await asyncFunction(data: data) { self.presentationMode.wrappedValue.dismiss() } } } label: { Text("Save") }
异步函数代码如下:
func asyncFunction(data: Data, completion: @escaping () -> Void) async { // Prepare URL let url = URL(string: "api-url") guard let requestUrl = url else { fatalError() } // HTTP Request payload which will be sent in HTTP Request Body let d = try! JSONEncoder().encode(data) ... // Perform HTTP Request let (responseData, _) = try! await URLSession.shared.data(for: request) // Convert HTTP Response Data to a String if let responseData = Optional(responseData) { if let dataString = String(data: responseData, encoding: .utf8) { print("Response:\n \(dataString)") } } // Call the completion handler completion() }
请求完成后调用dismiss()时应用崩溃,错误信息为:Thread 18: EXC_BREAKPOINT (code=1, subcode=0x11022cd24),怀疑是闭包问题,但不清楚具体原因。
问题原因与修复方案
核心崩溃原因
- UI操作不在主线程:
URLSession.data(for:)的回调会在后台线程执行,直接调用completion()会导致dismiss()这个UI操作在后台线程执行,违反SwiftUI必须在主线程更新UI的规则,引发崩溃。 - async函数与completion闭包混用:
async标记的函数本身是为了替代传统completion回调模式,混用会导致线程调度逻辑混乱,增加意外风险。
修复方案
方案一:移除completion闭包,使用async/await原生语法(推荐)
重构异步函数,去掉completion,直接在函数完成后回到主线程执行dismiss:
// 重构后的异步函数 func asyncFunction(data: Data) async throws { guard let requestUrl = URL(string: "api-url") else { throw NSError(domain: "InvalidURL", code: -1) } let d = try JSONEncoder().encode(data) var request = URLRequest(url: requestUrl) request.httpMethod = "POST" request.httpBody = d request.setValue("application/json", forHTTPHeaderField: "Content-Type") let (responseData, _) = try await URLSession.shared.data(for: request) if let dataString = String(data: responseData, encoding: .utf8) { print("Response:\n \(dataString)") } } // 视图中的Button点击逻辑 Button { Task { do { try await asyncFunction(data: data) // 回到主线程执行UI操作 await MainActor.run { presentationMode.wrappedValue.dismiss() } } catch { print("请求失败:\(error)") } } } label: { Text("Save") }
方案二:保留completion但确保在主线程调用
如果一定要用completion闭包,必须在调用时切换到主线程:
func asyncFunction(data: Data, completion: @escaping () -> Void) async { // ... 原有代码 ... // 在主线程调用completion await MainActor.run { completion() } }
额外优化建议
- 替换
try!为do-catch:强制解包try!会在编码失败或请求失败时直接崩溃,改用do-catch处理错误,提升应用稳定性。 - 改用新的
@Environment(\.dismiss):SwiftUI 3及以上版本推荐使用@Environment(\.dismiss) var dismiss替代presentationMode,调用时直接dismiss()即可,语法更简洁。
内容的提问来源于stack exchange,提问作者Hiimdjango
相关产品推荐
相关产品推荐

