I am working on a practice project with the CoinGecko API. I just figured out how to successfully fetch, parse, and display data from the API, but I have ran into an issue. Currently I am only fetching a single coin, using a single URL but I would like to be able to fetch multiple (3 in this case), all at once and display them. Not sure how to proceed, or if my explanation makes sense so I have attached my code below. Thanks in advance.
CoinListViewModel
import Foundation
class CoinListViewModel {
private(set) var coin: Coin
init(coin: Coin) {
self.coin = coin
}
func getCoins(url: URL) async {
do {
let coin = try await WebService().getCoins(url: url)
self.coin = coin
} catch {
print(error)
}
}
}
struct CoinViewModel {
private let coin: Coin
init(coin: Coin) {
self.coin = coin
}
var symbol: String {
coin.symbol
}
var name: String {
coin.name
}
var price: [String: Double] {
coin.marketData.currentPrice
}
}
WebService
import Foundation
enum CoinsError: Error {
case invalidServerResponse
}
class WebService {
func getCoins(url: URL) async throws -> Coin {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw CoinsError.invalidServerResponse
}
return try JSONDecoder().decode(Coin.self, from: data)
}
}
Constants
import Foundation
struct Constants {
struct coinURLs {
static let allCoinURLs = [
URL(string: "https://api.coingecko.com/api/v3/coins/bitcoin")!,
URL(string: "https://api.coingecko.com/api/v3/coins/ethereum")!,
URL(string: "https://api.coingecko.com/api/v3/coins/litecoin")!
]
}
}
Coin
import Foundation
// MARK: - WelcomeElement
struct Coin: Codable {
let symbol, name: String
let marketData: MarketData
enum CodingKeys: String, CodingKey {
case symbol, name
case marketData = "market_data"
}
static var DefaultCoin = Coin(symbol: " ", name: " ", marketData: MarketData(currentPrice: ["usd": 0.0]))
}
struct Tion: Codable {
let en: String
enum CodingKeys: String, CodingKey {
case en
}
}
struct MarketData: Codable {
let currentPrice: [String: Double]
enum CodingKeys: String, CodingKey {
case currentPrice = "current_price"
}
}
CoinListViewController
import Foundation
import UIKit
class CoinListViewController: UITableViewController {
private let vm = CoinListViewModel(coin: Coin.DefaultCoin)
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
Task {
await getCoins()
}
}
private func configureUI() {
self.navigationController?.navigationBar.prefersLargeTitles = true
self.title = "CoinFlip"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "StockCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StockCell", for: indexPath)
let coin = vm.coin
var content = cell.defaultContentConfiguration()
content.text = coin.name
content.secondaryText = "$\(Int(coin.marketData.currentPrice["usd"] ?? 0))"
cell.contentConfiguration = content
return cell
}
private func getCoins() async {
await vm.getCoins(url: Constants.coinURLs.allCoinURLs[0])
print(vm.coin)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
CodePudding user response:
You can spawn multiple network calls if you make a few changes to your WebService
. This will also return a partial result if fetching a coin fails.
func getCoins(urls: [URL]) async throws -> [Coin] {
return try await withThrowingTaskGroup(of: Coin.self, body: { taskGroup in
var coins = [Coin]()
urls.forEach { url in
taskGroup.addTask {
let (data, response) = try await URLSession.shared.data(from: url)
guard
let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200
else { return Coin() }
return try JSONDecoder().decode(Coin.self, from: data)
}
}
for try await coin in taskGroup {
coins.append(coin)
}
return coins
})
}