I am trying to get value from JSON type, bring it to my var cryptos. But nothing change, var cryptos still nil. Even value of cryptos in Update void has changed. I have struggled with this problem for many hours. Thanks for your answer. This is my code:
var cryptos: Crypto? = nil
override func viewDidLoad() {
super.viewDidLoad()
update()
//I can't print this. ERROR: Unexpectedly found nil while unwrapping an Optional value
//print(self.cryptos!.data[0].name)
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view.
}
@objc func update() {
if let url = URL(string:"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest") {
var request = URLRequest(url: url)
request.addValue("305782b4-...-1835adfe147a", forHTTPHeaderField: "X-CMC_PRO_API_KEY")
request.httpMethod = "GET"
let dataTask = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
do {
do {
let response = try JSONDecoder().decode(Crypto.self, from: data!)
self.cryptos = response
//but here I can. Why??
print(self.cryptos!.data[0].name)
} catch { print(error) }
}
}
dataTask.resume()
}
}
This is my Struct:
public struct Crypto : Codable {
struct data : Codable {
let id: Int
let name: String
let symbol: String
let cmc_rank: Int
let slug: String
struct quote : Codable {
struct USD : Codable {
let price: Double
let volume_24h: Double
let percent_change_1h: Double
let percent_change_24h: Double
let percent_change_7d: Double
}
let USD: USD
}
let quote: quote
}
let data: [data]
}
The update
runs asynchronously. So supply it a completion handler:
A few suggestions:
I'd define a response object to wrap the payload. The data
key is not something you need to perpetuate in your model objects:
public struct ResponseObject<T: Codable>: Codable {
let data: [T]
}
struct Crypto: Codable {
let id: Int
let name: String
let symbol: String
let cmc_rank: Int
let slug: String
struct Quote : Codable {
struct USD : Codable {
let price: Double
let volume_24h: Double
let percent_change_1h: Double
let percent_change_24h: Double
let percent_change_7d: Double
}
let USD: USD
}
let quote: Quote
}
I'd be inclined to use the above generic pattern, so you can reuse this ResponseObject
pattern elsewhere.
I'd give update
a completion handler that uses the Result
type:
@discardableResult
func update(completion: @escaping (Result<[Crypto], Error>) -> Void) -> URLSessionTask {
let url = URL(string:"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest")!
var request = URLRequest(url: url)
request.addValue("305782b4-...-1835adfe147a", forHTTPHeaderField: "X-CMC_PRO_API_KEY")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let responseData = data, error == nil else {
DispatchQueue.main.async { completion(.failure(error ?? NetworkError.unknownError(data, response))) }
return
}
do {
let response = try JSONDecoder().decode(ResponseObject<Crypto>.self, from: responseData)
DispatchQueue.main.async { completion(.success(response.data)) }
} catch {
DispatchQueue.main.async { completion(.failure(error)) }
}
}
task.resume()
return task
}
Where
enum NetworkError: Error {
case unknownError(Data?, URLResponse?)
}
FYI, in addition to a completion handler closure, I've removed the force unwrapping operator (!
).
I'd also make this return the URLSessionTask
(in case the caller might, at some future date, want to be able to cancel the request for any reason). By making it @discardableResult
, it means that you don't require the caller to do anything with the returned value, but you're leaving that door open for the future.
The viewDidLoad
could then use that to reload the table as appropriate:
var cryptos: [Crypto] = []
override func viewDidLoad() {
super.viewDidLoad()
update() { result in
switch result {
case .failure(let error):
print(error)
case .success(let cryptos):
self.cryptos = cryptos
self.tableView.reloadData()
}
}
}
Note, both the update of the model object and the reloading of the table are done in this completion handler, which has been dispatched to the main queue. All UI and model updates should take place on main thread.