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
相关产品推荐
相关产品推荐

