Не удалось преобразовать значение типа NSNull в NSString, а затем приложение вылетело - PullRequest
0 голосов
/ 01 августа 2020

Мы пытаемся создать функцию для получения JSON из API. Мы знаем, что это дает нам NIL, но мы не знаем, почему возникает ошибка. Точное сообщение об ошибке, которое мы получили, было

[] 2020-08-01 16: 29: 26.501199-0400 HEFT [97766: 2952325] [] nw_proxy_resolver_create_parsed_array [C1 proxy 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()
        }
    }

Ответы [ 2 ]

1 голос
/ 02 августа 2020

В некоторых из этих предупреждений 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. Вы не хотите взламывать sh, если сервер временно недоступен.

  2. В документах говорится, что вы должны установить User-Agent, поэтому я м делаю это выше. Очевидно, установите строковые константы domain и email соответственно.

  3. Хотя вы можете создать URL-адрес вручную, безопаснее всего использовать URLComponents, так как это позаботится о любых процент экранирования, который может понадобиться. Здесь это не обязательно, но будет полезным шаблоном, если вы начнете получать более сложные запросы (например, нужно указать название города с пробелом, например «Лос-Анджелес»).

  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. Это упрощает отслеживание кода и использует шаблон «раннего выхода», при котором вы можете легко ie код выхода с ошибкой (если есть).

Лично я бы посоветовал вам сделать еще один шаг и отказаться от ручного анализа результатов 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
}
0 голосов
/ 02 августа 2020

Короткий ответ заключается в том, что вы принудительно приводите все и принимаете очень специфичный формат c, которого нет в 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 ?? ""

Я бы посоветовал вам провести широкий рефакторинг вашей функции

...