【问题标题】:Could not cast value of type 'NSNull' to 'NSString' and then the app crashes无法将“NSNull”类型的值转换为“NSString”,然后应用程序崩溃
【发布时间】:2020-08-01 20:44:43
【问题描述】:

我们正在尝试创建一个从 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()
        }
    }

【问题讨论】:

  • 请注意:您可以使用guard-else,而不是多个嵌套的if-else 语句。之后您的代码可能更具可读性。
  • as! String 不允许 nil 或省略。它需要一个字符串实例。这可能是问题吗?错误到底发生在哪一行?
  • 显然您认为某个值应该是NSString,但它在您的 JSON 中为空。如果没有看到 JSON,我们无法回答这个问题。 FWIW,作为一般规则,我建议避免使用 as! 强制转换。
  • 其中一些警报没有说明。因此,您对 "instruction" 的强制施法失败。

标签: json swift xcode api swift-optionals


【解决方案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. The docs 说你应该设置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. 我已经用 guard 语句替换了 if 语句的塔。它使代码更易于遵循,并采用“提前退出”模式,您可以更轻松地将退出代码与失败(如果有)联系起来。


就个人而言,我建议您更进一步,不要手动解析 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
}

【讨论】:

  • 酷,你为他写了整个应用程序! :D +1 努力!
【解决方案2】:

简短的回答是因为你强制转换所有内容并假设 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 ?? ""

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

【讨论】:

  • 你能解释一下你做了什么不同的事情来完成这项工作吗?它奏效了,我们是初学者,想学习。
  • @Codestudio365 -此答案使用as?,因此如果找不到该值(或找到,但NSNull,如您的情况),它不会失败。话虽如此,这种使用nil 合并运算符?? 将所有缺失值默认为零长度字符串的模式并不是一个很好的模式。如果这些字段可能不存在,它们应该是可选的并且丢失?? "" 模式。
  • @rob 是的,完全同意这不好,因此“我敦促您广泛地重构您的功能”。主要是想显示它崩溃的地方。 -- 我没有得到一个有用的正确答案的反对票,但是 耸耸肩
猜你喜欢
  • 2016-06-06
  • 2017-04-10
  • 2016-11-17
  • 2019-05-02
  • 1970-01-01
  • 1970-01-01
  • 2017-10-30
  • 2018-02-08
  • 2021-01-14
相关资源
最近更新 更多