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

Swift JSONDecoder天气API解码异常求助:部分城市无法解析

问题排查与修复方案

核心问题分析

你的天气应用部分城市解码失败,主要由以下几个关键问题导致:

1. 天气文本枚举未覆盖所有可能值

Text枚举仅定义了有限的天气状况字符串,但weatherapi返回的天气文本种类远不止这些(比如"Moderate rain"、"Heavy snow"等)。当API返回枚举中未定义的文本时,JSONDecoder会直接解码失败。

2. URL拼接语法错误

ViewController中构建请求URL时存在两处错误:

  • 参数键名错误:=(cityName)应该是&q=\(cityName)(q是weatherapi指定的城市查询参数)
  • 字符串插值缺失反斜杠:正确的插值语法是\(变量名),而非(变量名)

3. 解码错误未捕获

使用try?会忽略解码过程中的具体错误信息,无法定位到底是哪个字段解码失败,不利于排查问题。

4. UI文本插值语法错误

设置UI标签文本时,字符串插值缺少反斜杠,导致无法正确显示数据(虽然不影响解码,但属于功能bug)。


修复后的代码示例

1. 修改天气文本类型(解决枚举覆盖不全问题)

将Text枚举替换为普通字符串类型,彻底避免因未覆盖值导致的解码失败:

// MARK: - Condition
struct Condition: Codable {
    let text: String  // 替换原有的Text枚举
    let icon: String
    let code: Int
}

如果仍想保留枚举做类型约束,可以添加未知值处理逻辑:

enum Text: String, Codable {
    case clear = "Clear"
    case cloudy = "Cloudy"
    case lightRainShower = "Light rain shower"
    case overcast = "Overcast"
    case partlyCloudy = "Partly cloudy"
    case patchyRainPossible = "Patchy rain possible"
    case sunny = "Sunny"
    case unknown  // 新增未知值case
    
    // 自定义解码逻辑,遇到未定义值时返回unknown
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let rawValue = try container.decode(String.self)
        self = Text(rawValue: rawValue) ?? .unknown
    }
}

2. 修正URL拼接与请求代码

override func viewDidLoad() {
    super.viewDidLoad()
    
    // ... 原有UI设置代码 ...
    
    // 修正URL拼接:替换错误的参数键名与插值语法
    let apiKey = "<api-key>"
    let cityName = "Beijing" // 示例城市,可替换为动态值
    guard let url = URL(string: "https://api.weatherapi.com/v1/forecast.json?key=\(apiKey)&q=\(cityName)&days=3&aqi=no&alerts=no") else { 
        print("URL构建失败")
        return 
    }
    
    // 使用单例NetworkManager,避免重复实例化
    NetworkManager.shared.downloadData(url: url) { data in
        guard let data = data else { return }
        self.parse(data: data)
    }
}

3. 完善解码错误捕获

private func parse(data: Data) {
    do {
        let weatherModel = try JSONDecoder().decode(WeatherModel.self, from: data)
        self.weatherModel = weatherModel
        
        DispatchQueue.main.async {
            guard let weatherModel = self.weatherModel else { return }
            
            self.cityLabel.text = weatherModel.location.name
            self.currentTemp.text = String(weatherModel.current.tempC)
            self.conditionLabel.text = weatherModel.current.condition.text
            
            self.currentDate.text = weatherModel.location.localtime
            // 修正字符串插值语法
            self.currentHumidity.text = "\(weatherModel.current.humidity)% \nHumidity"
            self.currentWind.text = "\(weatherModel.current.windKph) kph \nWind"
            self.currentFeelslike.text = "\(weatherModel.current.feelslikeC)° \nFeels Like"
            // 修正Icon URL插值
            if let iconUrl = URL(string: "https:\(weatherModel.current.condition.icon)") {
                self.imageView.kf.setImage(with: iconUrl)
            }
            
            self.collectionView.reloadData()
        }
    } catch {
        // 打印详细错误信息,方便排查
        print("解码失败:\(error.localizedDescription)")
        print("错误详情:\(error)")
    }
}

4. 优化NetworkManager(可选)

私有化初始化方法,强制使用单例避免重复实例化:

struct NetworkManager {
    static let shared = NetworkManager()
    
    private init() {} // 私有化初始化方法
    
    func downloadData(url: URL, completion: @escaping (Data?) -> Void) {
        AF.request(url).response { result in
            if let error = result.error {
                print("网络请求错误:\(error.localizedDescription)")
                completion(nil)
            } else {
                completion(result.data)
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Yavuz Ulgar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 08:42:24