В некоторых из этих предупреждений 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
}
Несколько других наблюдений:
Я удалил все принудительное приведение (as!
). Вы не хотите, чтобы ваше приложение падало, если на сервере возникла проблема. Например, нередко я получаю ошибку 503. Вы не хотите взламывать sh, если сервер временно недоступен.
В документах говорится, что вы должны установить User-Agent
, поэтому я м делаю это выше. Очевидно, установите строковые константы domain
и email
соответственно.
Хотя вы можете создать URL-адрес вручную, безопаснее всего использовать URLComponents
, так как это позаботится о любых процент экранирования, который может понадобиться. Здесь это не обязательно, но будет полезным шаблоном, если вы начнете получать более сложные запросы (например, нужно указать название города с пробелом, например «Лос-Анджелес»).
Я бы предложил вышеуказанный шаблон обработчика завершения, чтобы вызывающий мог знать, когда запрос выполнен. Таким образом, вы можете сделать что-то вроде:
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
}
}
Я возвращаю URLSessionTask
на случай, если вы захотите отменить запрос (например, пользователь отклоняет рассматриваемое представление), но я пометил его как @discardableResult
, так что вам не нужно его использовать, если вы не хотите.
Я заменил башню 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
}