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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 20:22:39