如何在 SWIFT 5 中更改对象结构的值

How to change value of a Object Struct in SWIFT 5

我正在尝试从 JSON 类型中获取值,将它带到我的 var cryptos。但没有任何变化,var cryptos 仍然为零。甚至 Update void 中的密码值也发生了变化。我已经为这个问题苦苦挣扎了好几个小时。感谢您的回答。 这是我的代码:

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()
        }
}

这是我的结构:

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]
}

向函数添加一个完成块,因为它是一个异步任务。

override func viewDidLoad() {
    super.viewDidLoad()
    update {
        print(self.cryptos?.data[0].name ?? "")
    }
    //...
}
@objc func update(completion: @escaping () -> Void) {
    if let url = URL(string:"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest") {
        //...
        let dataTask = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
            do {
                let response = try JSONDecoder().decode(Crypto.self, from: data!)
                self.cryptos = response
                print(self.cryptos!.data[0].name)
                completion()
            } catch {
                print(error)
                completion()
            }
        }
        dataTask.resume()
    }
}

update 异步运行。所以为它提供一个完成处理程序:


几点建议:

  1. 我会定义一个响应对象来包装负载。 data 键不是您需要在模型对象中永久保留的东西:

     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
     }
    

    我倾向于使用上面的通用模式,因此您可以在别处重用这个 ResponseObject 模式。

  2. 我会给 update 一个使用 Result 类型的完成处理程序:

     @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
     }
    

    在哪里

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

    仅供参考,除了完成处理程序闭包外,我还删除了强制展开运算符 (!)。

    我还会将此 return 设为 URLSessionTask(以防来电者在将来的某个日期希望能够出于任何原因取消请求)。通过使其成为 @discardableResult,这意味着您不需要调用者对 returned 值执行任何操作,但您为将来打开了大门。

  3. 然后 viewDidLoad 可以使用它来根据需要重新加载 table:

     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()
             }
         }
     }
    

    请注意,模型对象的更新和 table 的重新加载都在这个已被分派到主队列的完成处理程序中完成。所有 UI 和模型更新都应在主线程上进行。