Search code examples
swiftasynchronousswiftuidelay

Fetching API data asynchronously and making calls every 1 second


I have a function:

func fetchHistoricalCurrencyData(completion: @escaping (() -> ())) {
    let g = DispatchGroup()
    DispatchQueue.main.async { [weak self] in
        var historicalCurrencyDataTemp = [HistoricalCurrencyDataModel]()
        let dayDurationInSeconds: TimeInterval = 60*60*24
        for date in stride(from: self!.startingDate, to: self!.endingDate, by: dayDurationInSeconds) {
            g.enter()
            APIManager.shared.getHistoricalCurrencyData(date: self!.convertDateToStringDate(date: date), fromCurrency: String(self!.fromCurrency.prefix(3)), amount: self!.amount, toCurrency: String(self!.toCurrency.prefix(3))) {
                historicalCurrencyDataModel in
                historicalCurrencyDataTemp.append(historicalCurrencyDataModel)
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    g.leave()
                }
            }
        }
        g.notify(queue:.main) {
            self?.historicalCurrencyData = DataManager.shared.getHistoricalCurrencyData(historicalCurrencyDataModels: historicalCurrencyDataTemp)
            print(self?.historicalCurrencyData)
            completion()
        }
        
    }
}

What's more, I have a dateRange which I divide into particular days within this range by using stride() function. Every day I get from this function I use to make an API Call to get desired data. Every API data I get is being converted to particular HistoricalCurrencyDataModel and appended to temp array which I create before the loop (historicalCurrencyDataTemp). When I receive all the data I want I use DataManager class to transform this data into [String: Double] dictionary which I finally assign to my @Published var historicalCurrencyData: [String: Double] variable. One clue thing is that API I use make it possible to make API calls one after another at least after 1 second so I have to create 1 second delay between each call. I made it possible with DispatchQueue.main.asyncAfter(deadline: .now() + 1) statement. I control the whole flow of asynchronous operations with DispatchGroup(). Unfortunately after all of this what I get is empty Optional([:]) within print(self?.historicalCurrencyData) statement. And for example the correct behaviour would be that if I use dateRange with 5 days I get a dictionary with 5 entries.


Solution

  • I ultimately got rid of all DispatchQueues and now it works as it should. I believe there was something wrong with the delay and not all values were fetched correctly. What's more, I discovered that there is no need of creating 1 second between API calls I thought otherwise after reading API documentation. I must have been wrong.

    As suggested (which I agree with) I attach working code:

    func fetchHistoricalCurrencyData(completion: @escaping (() -> ())) {
        let g = DispatchGroup()
        var historicalCurrencyDataTemp = [HistoricalCurrencyDataModel]()
        for date in stride(from: self.startingDate, to: self.endingDate, by: dayDurationInSeconds) {
            g.enter()
            APIManager.shared.getHistoricalCurrencyData(date: self.convertDateToStringDate(date: date), fromCurrency: String(self.fromCurrency.prefix(3)), amount: self.amount, toCurrency: String(self.toCurrency.prefix(3))) {
                historicalCurrencyDataModel in
                historicalCurrencyDataTemp.append(historicalCurrencyDataModel)
                g.leave()
            }
        }
        g.notify(queue:.main) {
            self.historicalCurrencyData = DataManager.shared.getHistoricalCurrencyData(historicalCurrencyDataModels: historicalCurrencyDataTemp)
            print(self.historicalCurrencyData)
            completion()
        }      
    }