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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 18:18:02