API 调用期间 Swift valueNotFound 的问题

Issues with Swift valueNotFound during API Call

我正在寻求有关 Swift 项目的帮助。我正在构建一个从 API 中提取航空天气数据的应用程序,这种情况很常见:

用户需要机场气象站 KDAB 的数据 - 当前报告说:

用户需要来自机场气象站 KJAX 的数据 - 当前报告说:

在这个简单的例子中,您可能会注意到,在此报告期间没有为 KJAX 提供阵风数据,也没有为 KDAB 指定“特殊天气”(即雨、霾、雾)。我的应用程序需要能够处理“无”或未提供的数据,而不仅仅是告诉我存在 valueNotFound 或索引超出范围。

这是 API 文档:https://avwx.docs.apiary.io/#reference/0/metar/get-metar-report

这是我的代码:


import Foundation

struct WeatherManager {
    let weatherURL = "https://avwx.rest/api/metar/"

    func fetchWeather (stationICAO: String) {
        let urlString = "\(weatherURL)\(stationICAO)?token=OVi45FiTDo1LmyodShfOfoizNe5m9wyuO6Mkc95AN-c"
        performRequest(urlString: urlString)
    }
    
    func performRequest (urlString: String) {
        if let url = URL(string: urlString) {
            let session = URLSession(configuration: .default)
                
            
            let task = session.dataTask(with: url) { (data, response, error) in
                if error != nil {
                    print(error!)
                    return
                }
                
                if let safeData = data {
                    self.parseJSON(weatherData: safeData)
                }
            }
            
            task.resume()
            print(urlString)
            
            
            }
        }
    
    func parseJSON(weatherData: Data) {
        
        
        do {
            let decoder = JSONDecoder()
            let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
            
            
            
            let lowCloudsType = decodedData.clouds[0].type
            let midCloudsType = decodedData.clouds[1].type 
            let highCloudsType = decodedData.clouds[2].type 
            let lowCloudsAlt = decodedData.clouds[0].altitude
            let midCloudsAlt = decodedData.clouds[1].altitude 
            let highCloudsAlt = decodedData.clouds[2].altitude 
            let reportingStationVar = decodedData.station 
            let windGustValue = decodedData.wind_gust.value 
            let windSpeedValue = decodedData.wind_speed.value 
            let windDirectionValue = decodedData.wind_direction.value 
            let visibilityValue = decodedData.visibility.value
            let flightRulesValue = decodedData.flight_rules
            
            let weather = WeatherModel(lowestCloudsType: lowCloudsType, lowestCloudsAlt: lowCloudsAlt, middleCloudsType: midCloudsType, middleCloudsAlt: midCloudsAlt, highestCloudsType: highCloudsType, highestCloudsAlt: highCloudsAlt, reportingStation: reportingStationVar, windGust: windGustValue, windSpeed: windSpeedValue, windDirection: windDirectionValue, visibility: visibilityValue, flightRules: flightRulesValue)
            
            print(weather.flightConditions)
            
        } catch {
            print(error)
        }
    }
    
    

}
    
    

import Foundation

struct WeatherModel {
    
    
    let lowestCloudsType: String
    let lowestCloudsAlt: Int
    let middleCloudsType: String
    let middleCloudsAlt: Int
    let highestCloudsType: String
    let highestCloudsAlt: Int
    let reportingStation: String
    let windGust: Int
    let windSpeed: Int
    let windDirection: Int
    let visibility: Int
    let flightRules: String
    
    var flightConditions: String {
        switch flightRules {
        case "VFR":
            return "green"
        case "MVFR":
            return "blue"
        case "IFR":
            return "red"
        case "LIFR":
            return "purple"
        default:
            return "gray"
        
        }
    }
}

最后一个:


import Foundation

struct WeatherData: Decodable {
   
    
    let clouds: [Clouds]
    let flight_rules: String
    let remarks: String
    let wind_speed: WindSpeed
    let wind_gust: WindGust
    let wind_direction: WindDirection
    let visibility: Visibility

    let station: String
}



struct Clouds: Decodable {
    let type: String
    let altitude: Int
}

struct WindSpeed: Decodable {
    let value: Int
}

struct WindGust: Decodable {
    let value: Int
}

struct WindDirection: Decodable {
    let value: Int
}

struct Visibility: Decodable {
    let value: Int
}


根据我玩的是什么,当我进入一个没有我需要能够呈现给如果天气服务报告了用户。

2020-09-22 02:47:58.930421-0400 AvWx Pro[66612:4483807] libMobileGestalt MobileGestaltCache.c:38: No persisted cache on this platform.
KDAB
https://avwx.rest/api/metar/KDAB?token=(mySecretToken)
2020-09-22 02:48:02.943231-0400 AvWx Pro[66612:4483809] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
valueNotFound(Swift.KeyedDecodingContainer<AvWx_Pro.WindGust.(unknown context at 53fb3b8).CodingKeys>, 
Swift.DecodingError.Context(codingPath: 
[CodingKeys(stringValue: "wind_gust", intValue: nil)], 
debugDescription: "Cannot get keyed decoding container 
-- found null value instead.", underlyingError: nil))

当我使用没有报告所有三个可能的云层的机场时出现不同的错误:

2020-09-22 03:06:02.398628-0400 AvWx Pro[66736:4497432] libMobileGestalt MobileGestaltCache.c:38: No persisted cache on this platform.
KJAX
https://avwx.rest/api/metar/KJAX?token=(mySecretKey)
2020-09-22 03:06:07.955064-0400 AvWx Pro[66736:4497429] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Fatal error: Index out of range: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1200.2.22.2/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
2020-09-22 03:06:08.908826-0400 AvWx Pro[66736:4497429] Fatal error: Index out of range: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1200.2.22.2/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
(lldb) 

我现在已经花了几个小时尝试我在网上找到的各种解决方案,包括使用可选和强制展开、使用 guard let、使用 if let 和其他一些。我现在很迷茫。

我是这个平台的新手(作为发帖人),非常感谢任何人提供的任何见解!提前感谢您的帮助。

答案是使用 Optionals

考虑下面的游乐场示例,年龄和位置属性是可选的,JSON 中缺少位置 属性。

import Foundation

struct TestModel: Decodable {

    let name: String
    let age: Int?
    let location: String?

    enum CodingKeys: String, CodingKey {
            case name
            case age
            case location
    }
}

let jsonData = """
{
    "name": "John Smith",
    "age": 28
}
""".data(using: .utf8)!

let result = try JSONDecoder().decode(TestModel.self, from: jsonData)
print(result)

输出

TestModel(name: "John Smith", age: Optional(28), location: nil)

为避免解码时崩溃 json 您应该正确配置结构并检查访问值之前的字段:

  1. 可选字段

[CodingKeys(stringValue: "wind_gust", intValue: nil)], debugDescription: "Cannot get keyed decoding container -- found null value instead.", underlyingError: nil)) wind_gust can be empty so you should make it optional:

如果响应中的字段可以为空或 null,您应该在结构中将其设为可选,例如:

struct WeatherData: Decodable { 
    ...
    let wind_gust: WindGust?
    ...
}

然后在您的代码中,如果 wind_gust 存在,则只需使用可选绑定来提取一个值:

    if let value = decodedData.wind_gust?.value {
        print(value)
    }
  1. 数组

Fatal error: Index out of range

您必须始终在访问项目之前检查数组的边界,例如:

let clouds = decodedData.clouds
let lowCloudsType = clouds.count > 0 ? clouds[0].type : nil
let midCloudsType = clouds.count > 1 ? clouds[1].type : nil
let highCloudsType = clouds.count > 2 ? clouds[2].type : nil

if let low = lowCloudsType, let mid = midCloudsType, let high = highCloudsType {
    print(low)
    print(mid)
    print(high)
}