【问题标题】:Fetch data from different APIs through the same struct?通过同一个结构从不同的 API 获取数据?
【发布时间】:2018-06-21 04:55:04
【问题描述】:

是否可以通过相同的结构从不同但相似的 API(不同的 URL 但相似的 JSON 结构)获取数据?

例如通过bitcoinUSD.raw.eth.usd.pricebitcoinUSD.raw.eth.gbp.price访问数据:

struct Bitcoin : Decodable {
    private enum CodingKeys : String, CodingKey { case raw = "RAW" }
    let raw : BitcoinRAW
}

struct BitcoinRAW : Decodable {
    private enum CodingKeys : String, CodingKey { case btc = "BTC" }
    let btc : BitcoinETH
}

struct BitcoinETH : Decodable {
    private enum CodingKeys : String, CodingKey {
        case usd = "USD"
        case gbp = "GBP"
    }
    let usd : BitcoinUSD
    let gbp : BitcoinGBP
}

struct BitcoinUSD : Decodable {
    let price : Double
    let percentChange24h : Double

    private enum CodingKeys : String, CodingKey {
        case price = "PRICE"
        case percentChange24h = "CHANGEPCT24HOUR"
    }
}

struct BitcoinGBP : Decodable {
    let price : Double
    let percentChange24h : Double

    private enum CodingKeys : String, CodingKey {
        case price = "PRICE"
        case percentChange24h = "CHANGEPCT24HOUR"
    }
}

这是我的数据获取函数:

enum MyErrorBTC : Error {
    case FoundNil(String)
}

class BitcoinInfo : NSObject {

    static var btcURL : URL?

    func fetchBitcoinInfo(completion: @escaping (Bitcoin?, Error?) -> Void) {

        if WalletViewController.currencyUSD == true {
            BitcoinInfo.btcURL = URL(string: "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC&tsyms=USD")!
            let task = URLSession.shared.dataTask(with: BitcoinInfo.btcURL!) { (data, response, error) in
                guard let data = data else { return }
                do {
                    if let bitcoinPrice = try? JSONDecoder().decode(Bitcoin.self, from: data) {
                        completion(bitcoinPrice, nil)
                        //print("price =", bitcoinUSD.raw.btc.usd.price)
                        throw MyErrorBTC.FoundNil("bitcoinPrice")
                    }
                } catch {
                    print(error)
                }
            }
            task.resume()
        } else if WalletViewController.currencyGBP == true {
            BitcoinInfo.btcURL = URL(string: "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC&tsyms=GBP")!
            let task = URLSession.shared.dataTask(with: BitcoinInfo.btcURL!) { (data, response, error) in
                guard let data = data else { return }
                do {
                    if let bitcoinPrice = try? JSONDecoder().decode(Bitcoin.self, from: data) {
                        completion(bitcoinPrice, nil)
                        //print("price =", bitcoinUSD.raw.btc.gbp.price)
                        throw MyErrorBTC.FoundNil("bitcoinPrice")
                    }
                } catch {
                    print(error)
                }
            }
            task.resume()
        }
    }
}

请注意,当调用任一函数时,此代码返回 nil。 有什么办法可以修改吗?

【问题讨论】:

  • 显示您的 API 获取函数,以便人们提供反馈。
  • @creeperspeak 我用完整代码编辑了我的问题

标签: swift api struct


【解决方案1】:

是的,但您应该将变量声明为可选:

struct BitcoinETH : Decodable {
    private enum CodingKeys : String, CodingKey {
        case usd = "USD"
        case gbp = "GBP"
    }
    let usd : BitcoinUSD?
    let gbp : BitcoinGBP?
}

如果没有,将抛出异常,并且您将获得整个结构的 nil 值,因为它不会像解码器预期的那样被解码

【讨论】:

  • 非常感谢 Eugene,我尝试了这个解决方案但没有成功,但我确信我必须尝试以错误的方式访问数据。
  • @Martin33000 你能告诉我,你为什么没有通过这种方法获得成功?因为我做了tsyms=GBP,则结构也将使用填充的 gbp 值正确解码,usd 将为 nil。如果您尝试 GBP,USD,两者都将正确填写,而对于其他类型,两者都将为 nil
  • 在这种情况下您是如何访问数据的?当我在func fetchBitcoinInfo 中尝试print("price =", bitcoinPrice.raw.btc.gbp.price) 时,它甚至都不会打印
  • @Martin3300 好吧,首先我已经改变了我在答案中显示的结构,其次,代码中的拼写错误很少:1. 使用 print("price = \(String(描述:bitcoinPrice.raw.btc.usd?.price))") 2. 将 throw 语句移至 else
  • 好的 1. 我更改了 print,2. 将 throw 移到 else 并 3. 修改结构以将变量声明为可选项。我仍然收到FoundNil("bitcoinPrice")
【解决方案2】:

如果您的目标只是让您的 fetch 函数更可重用,那么这两个端点之间的唯一区别似乎是货币。在这种情况下,为什么不直接将货币作为参数传递,并使用您获得的货币参数构建您的 URL。看起来像这样:

func fetchBitcoinInfo(forCurrency currency: String, _ completion: @escaping (Bitcoin?, Error?) -> Void) {
    BitcoinInfo.btcURL = URL(string: "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC&tsyms=\(currency)")!
    let task = URLSession.shared.dataTask(with: BitcoinInfo.ethURL!) { (data, response, error) in
        guard let data = data else { return }
        do {
            if let bitcoinPrice = try? JSONDecoder().decode(Bitcoin.self, from: data) {
                completion(bitcoinPrice, nil)
            } else {
                throw MyErrorBTC.FoundNil("bitcoinPrice")
            }
        } catch {
            print(error)
        }
    }
    task.resume()
}

顺便说一句,当您 得到一个有效的 Bitcoin 对象时,您正在抛出 FoundNil,这没有任何意义。我将您的错误抛出移动到 else 语句中,以便它仅在您的 bitcoinPrice 解包失败时抛出。

【讨论】:

  • 非常感谢爬行者!这是一个了不起的解决方案,我不知道您可以更改 URL!现在struct 呢?我将如何访问bitcoinUSD.raw.eth.usd.pricebitcoinUSD.raw.eth.gbp.price,具体取决于所选的货币?
猜你喜欢
  • 1970-01-01
  • 2021-05-11
  • 1970-01-01
  • 1970-01-01
  • 2016-01-07
  • 1970-01-01
  • 1970-01-01
  • 2020-10-01
  • 2016-06-27
相关资源
最近更新 更多