-1

我们正在尝试创建一个从 API 获取 JSON 的函数。我们知道这给了我们 NIL,但我们不知道为什么会发生错误。我们得到的确切错误信息是

[] 2020-08-01 16:29:26.501199-0400 HEFT [97766:2952325] [] nw_proxy_resolver_create_parsed_array [C1 代理 pac] 评估错误:NSURLErrorDomain:-1003 无法将类型“NSNull”(0x7fff87a92380)的值转换为“NSString” ' (0x7fff87b502e8)。2020-08-01 16:29:26.670549-0400 HEFT [97766:2952139] 无法将“NSNull”(0x7fff87a92380)类型的值转换为“NSString”(0x7fff87b502e8)。(lldb)

我们试图弄乱代码以找到解决方案,并尝试使用其他一些问题,但它们都与我们试图实现的目标无关。

func getJson() {
        if let url = URL(string: "https://api.weather.gov/alerts/active?area=GA") {
            URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?) in
                if error == nil {
                    if data != nil {
                        if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String:AnyObject] {
                            DispatchQueue.main.async {
                                
                                //if let rawfeatures = json["features"] {
                                var rawfeatures = json["features"] as! [Dictionary< String, AnyObject>]
                                var keepgoingfeatures = rawfeatures.count
                                var FeatureIndex = 0
                                while keepgoingfeatures != 0{
                                    let currentRawFeature = rawfeatures[FeatureIndex]
                                    let currentRawFeatureProperties = currentRawFeature["properties"]
                                    let currentFeature = Feature()
                                    currentFeature.event = currentRawFeatureProperties!["event"] as! String
                                    currentFeature.description = currentRawFeatureProperties!["description"] as! String
                                    currentFeature.instructions = currentRawFeatureProperties!["instruction"] as! String
                                    currentFeature.urgency = currentRawFeatureProperties!["urgency"] as! String
                                    keepgoingfeatures -= 1
                                    FeatureIndex += 1
                                }
                            }
                        }
                    }
                    
                    
                } else {
                    print("We have an error")
                }
            }.resume()
        }
    }
4

2 回答 2

1

其中一些警报null用于instructions. 我建议定义您的对象以承认该字段是可选的,即它可能不存在。例如

struct Feature {
    let event: String
    let description: String
    let instruction: String?
    let urgency: String
}

而且,在解析它时,我可能会建议摆脱所有那些强制展开运算符,例如

enum NetworkError: Error {
    case unknownError(Data?, URLResponse?)
    case invalidURL
}

@discardableResult
func getWeather(area: String, completion: @escaping (Result<[Feature], Error>) -> Void) -> URLSessionTask? {
    // prepare request

    var components = URLComponents(string: "https://api.weather.gov/alerts/active")!
    components.queryItems = [URLQueryItem(name: "area", value: area)]
    var request = URLRequest(url: components.url!)
    request.setValue("(\(domain), \(email))", forHTTPHeaderField: "User-Agent")

    // perform request

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard
            error == nil,
            let httpResponse = response as? HTTPURLResponse,
            200 ..< 300 ~= httpResponse.statusCode,
            let responseData = data,
            let responseDictionary = try? JSONSerialization.jsonObject(with: responseData) as? [String: Any],
            let rawFeatures = responseDictionary["features"] as? [[String: Any]]
        else {
            DispatchQueue.main.async {
                completion(.failure(error ?? NetworkError.unknownError(data, response)))
            }
            return
        }

        let features = rawFeatures.compactMap { feature -> Feature? in
            guard
                let properties = feature["properties"] as? [String: Any],
                let event = properties["event"] as? String,
                let description = properties["description"] as? String,
                let urgency = properties["urgency"] as? String
            else {
                print("required string absent!")
                return nil
            }
            let instruction = properties["instruction"] as? String

            return Feature(event: event, description: description, instruction: instruction, urgency: urgency)
        }

        DispatchQueue.main.async {
            completion(.success(features))
        }
    }

    task.resume()

    return task
}

其他一些观察:

  1. 我已经删除了所有强制转换(as!)。如果服务器出现问题,您不希望您的应用程序崩溃。例如,我经常收到 503 错误。如果服务器暂时不可用,您不希望崩溃。

  2. 文档说你应该设置User-Agent,所以我在上面做。显然,相应地设置domainemail字符串常量。

  3. 虽然您可以手动构建 URL,但使用它是最安全的URLComponents,因为这将处理可能需要的任何百分比转义。这里不需要它,但如果您开始处理更复杂的请求(例如,需要指定一个包含空格的城市名称,例如“Los Angeles”),它将是一个有用的模式。

  4. 我建议使用上述完成处理程序模式,以便调用者可以知道请求何时完成。所以你可能会做类似的事情:

     getWeather(area: "GA") { result in
         switch result {
         case .failure(let error):
             print(error)
             // update UI accordingly
    
         case .success(let features):
             self.features = features        // update your model object
             self.tableView.reloadData()     // update your UI (e.g. I'm assuming a table view, but do whatever is appropriate for your app
         }
     }
    
  5. URLSessionTask如果您可能想要取消请求(例如,用户关闭有问题的视图),我将返回,但我已将其标记为@discardableResult,因此如果您不想要,则不必使用它。

  6. 我已经用if声明替换了声明塔guard。它使代码更容易遵循并采用“提前退出”模式,您可以更轻松地将退出代码与失败(如果有)联系起来。


就个人而言,我建议您更进一步,不要手动解析JSONSerialization结果。JSONDecoder让您为您完成所有这些工作要容易得多。例如:

struct ResponseObject: Decodable {
    let features: [Feature]
}

struct Feature: Decodable {
    let properties: FeatureProperties
}

struct FeatureProperties: Decodable {
    let event: String?
    let description: String
    let instruction: String?
    let urgency: String
}

enum NetworkError: Error {
    case unknownError(Data?, URLResponse?)
    case invalidURL
}

@discardableResult
func getWeather(area: String, completion: @escaping (Result<[FeatureProperties], Error>) -> Void) -> URLSessionTask? {
    var components = URLComponents(string: "https://api.weather.gov/alerts/active")!
    components.queryItems = [URLQueryItem(name: "area", value: area)]
    var request = URLRequest(url: components.url!)
    request.setValue("(\(domain), \(email))", forHTTPHeaderField: "User-Agent")

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard
            error == nil,
            let httpResponse = response as? HTTPURLResponse,
            200 ..< 300 ~= httpResponse.statusCode,
            let responseData = data
        else {
            DispatchQueue.main.async {
                completion(.failure(error ?? NetworkError.unknownError(data, response)))
            }
            return
        }

        do {
            let responseObject = try JSONDecoder().decode(ResponseObject.self, from: responseData)
            DispatchQueue.main.async {
                completion(.success(responseObject.features.map { $0.properties }))
            }
        } catch let parseError {
            DispatchQueue.main.async {
                completion(.failure(parseError))
            }
        }
    }

    task.resume()

    return task
}
于 2020-08-02T19:35:53.667 回答
-1

简短的答案是因为您强制转换所有内容并假设 json 没有的非常特定的格式。

所以在某些时候,你会读到一个刚刚插入的值。具体指导。

作为一个工作/非崩溃修复(我在本地运行!):

let currentFeature = Feature()
currentFeature.event = currentRawFeatureProperties!["event"] as? String ?? ""
currentFeature.description = currentRawFeatureProperties!["description"] as? String ?? ""
currentFeature.instructions = currentRawFeatureProperties!["instruction"]  as? String ?? ""
currentFeature.urgency = currentRawFeatureProperties!["urgency"] as? String ?? ""

我敦促您广泛地重构您的功能

于 2020-08-01T21:06:50.080 回答