URLSession HTTP错误无法实时更新视图,需重复触发操作
问题分析与解决方案:API调用后视图不更新及异步逻辑错误
核心问题拆解
- 异步逻辑错误:
checkDriverIn使用URLSession.dataTask回调处理请求,但函数本身标记为async却在网络请求未完成时,就基于旧的httpError值返回布尔结果,导致第二次调用才用上一次的请求结果。 - UI更新失效:网络回调在后台线程执行,直接修改
@Published属性可能无法触发视图更新;另外HTTPError无需作为ObservableObject,反而会干扰@Published的状态发布逻辑。
解决方案步骤
1. 简化HTTPError类型
HTTPError只需作为普通Codable值类型即可,不需要继承ObservableObject,ViewModel中的@Published会负责发布整个对象的变化:
struct HTTPError: Codable { var statusCode: Int? var message: [String]? var error: String? init(statusCode: Int? = nil, message: [String]? = [], error: String? = nil) { self.statusCode = statusCode self.message = message ?? [] self.error = error } enum CodingKeys: String, CodingKey { case statusCode, message, error } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.statusCode = try container.decodeIfPresent(Int.self, forKey: .statusCode) // 兼容message字段为字符串或数组的情况 if let strMessage = try container.decodeIfPresent(String.self, forKey: .message) { self.message = [strMessage] } else { self.message = try container.decodeIfPresent([String].self, forKey: .message) ?? [] } self.error = try container.decodeIfPresent(String.self, forKey: .error) } }
2. 修正ViewModel的异步逻辑
改用URLSession的async/await版本,让函数等待网络请求完成后再返回结果,同时确保状态更新在主线程执行:
class VM: ObservableObject { @Published var isLoading = false @Published var httpError = HTTPError() func checkDriverIn(_ record: DriverQRParam) async -> Bool { isLoading = true defer { isLoading = false } // 请求结束后自动关闭加载状态 guard let url = URL(string: "你的API地址") else { httpError = HTTPError(error: "无效的请求地址") return false } var request = URLRequest(url: url) request.httpMethod = "POST" // 根据实际请求方式调整 request.setValue("application/json", forHTTPHeaderField: "Content-Type") do { // 编码请求体(根据你的DriverQRParam实现) let jsonData = try JSONEncoder().encode(record) request.httpBody = jsonData // 使用async/await版本的网络请求API let (data, response) = try await URLSession.shared.data(for: request) // 处理响应状态码 if let httpResponse = response as? HTTPURLResponse { switch httpResponse.statusCode { case 400: httpError = try JSONDecoder().decode(HTTPError.self, from: data) return false case 200: httpError = HTTPError() // 成功时重置错误状态 return true default: httpError = HTTPError(error: "请求失败,状态码: \(httpResponse.statusCode)") return false } } } catch { httpError = HTTPError(error: error.localizedDescription) return false } httpError = HTTPError(error: "请求处理异常") return false } }
3. 修正视图中的ViewModel声明
使用@StateObject而非@State管理ObservableObject的生命周期,确保视图能正确响应ViewModel的状态变化:
struct xyz: View { @StateObject private var vm = VM() @State private var driverParam = DriverQRParam() // 假设已完成初始化 var body: some View { VStack { // 展示错误信息 if let errorMsg = vm.httpError.error { Text(errorMsg) .foregroundColor(.red) .padding() } Button(vm.isLoading ? "加载中..." : "点击") { Task { let result = await vm.checkDriverIn(driverParam) // 根据请求结果执行后续逻辑,比如弹窗、导航等 print("请求结果: \(result)") } } .disabled(vm.isLoading) // 加载中禁用按钮,避免重复触发请求 } } }
关键说明
- async/await替代回调:彻底解决函数提前返回旧值的问题,确保返回结果为当前请求的最新状态。
- 自动主线程更新:
URLSession.shared.data(for:)在async上下文会自动切换回主线程,修改@Published属性时能直接触发视图更新。 - 值类型优化:
HTTPError改为struct符合Swift值语义,避免引用类型带来的意外状态共享问题。
内容的提问来源于stack exchange,提问作者Mohamed Said
相关产品推荐
相关产品推荐

