Swift/SwiftUI处理OpenWeatherMap API经纬度等JSON报错的方案优化问询
实现优化方案
现有实现的核心问题
你现在的方案最大的缺陷是:解码业务模型失败后重新发起了一次完全重复的网络请求,不仅浪费用户流量、增加请求耗时,极端情况下如果两次请求返回结果不一致还会出现逻辑bug,完全没必要,第一次请求已经拿到了完整的返回数据,直接复用即可。
优化步骤
1. 先完善错误相关定义
首先定义统一的错误模型和业务错误枚举,比纯字符串错误更易后续扩展处理:
// 接口返回的错误结构 struct ApiErrorMessage: Codable { let cod: String let message: String } // 自定义业务错误枚举 enum ApiError: Error { case invalidURL case networkError(Error) case apiError(code: String, message: String) case decodingError(String) } // 可选扩展:给ApiError加本地化描述方便直接展示给用户 extension ApiError: LocalizedError { var errorDescription: String? { switch self { case .invalidURL: return "无效的请求链接" case .networkError(let error): return "网络请求失败:\(error.localizedDescription)" case .apiError(let code, let message): return "接口请求错误(错误码:\(code)):\(message)" case .decodingError(let msg): return "数据解析失败:\(msg)" } } }
2. 优化请求逻辑,复用首次请求数据
不需要单独写第二个请求错误的函数,直接在同一个请求管道里先判断响应状态,再分支解码即可:
func getForecast<T: Decodable>(api url: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, completion: @escaping(Result<T, ApiError>) -> Void) { guard let url = URL(string: url) else { completion(.failure(.invalidURL)) return } let request = URLRequest(url: url) let decoder = JSONDecoder() decoder.dateDecodingStrategy = dateDecodingStrategy decoder.keyDecodingStrategy = keyDecodingStrategy URLSession.shared.dataTaskPublisher(for: request) // 先处理响应,验证状态码 .tryMap { data, response in guard let httpResponse = response as? HTTPURLResponse else { throw ApiError.networkError(NSError(domain: "InvalidResponse", code: 0)) } // 把响应状态码和原始数据一起往后传 return (data: data, statusCode: httpResponse.statusCode) } // 分支处理:非200状态码直接解错误模型,200解业务模型 .tryMap { result in if 200..<300 ~= result.statusCode { // 状态码正常,解码业务模型 do { return try decoder.decode(T.self, from: result.data) } catch { throw ApiError.decodingError(error.localizedDescription) } } else { // 状态码异常,解码错误模型 do { let errorMsg = try decoder.decode(ApiErrorMessage.self, from: result.data) throw ApiError.apiError(code: errorMsg.cod, message: errorMsg.message) } catch { throw ApiError.decodingError("错误信息解析失败:\(error.localizedDescription)") } } } .receive(on: DispatchQueue.main) .sink { completionStatus in switch completionStatus { case .finished: break case .failure(let error): // 把前面抛出的ApiError直接返回 if let apiError = error as? ApiError { completion(.failure(apiError)) } else { completion(.failure(.networkError(error))) } } } receiveValue: { decodedData in completion(.success(decodedData)) } .store(in: &anyCancellable) }
优化优势
- 全程只发起一次网络请求,没有冗余请求,性能更好
- 逻辑更统一,所有错误处理都在同一个链路里,后续维护更方便
- 错误枚举化,后续如果要根据不同错误码做不同处理(比如API key过期跳转到配置页),比纯字符串错误信息好扩展很多
- 兼容正常和异常返回的解析逻辑,不需要额外维护独立的错误请求函数
内容的提问来源于stack exchange,提问作者Ufuk Köşker
相关产品推荐
相关产品推荐

