Sweeper
Sweeper

Reputation: 271660

How to detect that all 30 Alamofire requests have all been responded to?

I am making an app that shows you the exchange rates between two currencies in the last 30 days by presenting the data in a chart. I am using Alamofire, together with SwiftyJSON to parse the response. The API that I am using is http://fixer.io.

In my view controller, I have this method called getRate that will populate a dictionary ([Date: Double]) called rates. getRate will be called in viewDidLoad and is defined as follows:

func getRate() {
    let baseCurrency = UserDefaults.standard.string(forKey: "baseCurrency")
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    for date in last30Days {
        let dateString = formatter.string(from: date)
        let url = "https://api.fixer.io/\(dateString)?base=\(baseCurrency!)&symbols=\(self.currency!)"
        Alamofire.request(url).responseString {
            [weak self]
            response in
            if let _ = response.error {
                return
            }

            let json = JSON(parseJSON: response.value!)
            if let _ = json["error"].string {
                return
            }

            if self != nil {
                if let rate = json["rates"][self!.currency.currencyCode].double {
                    self!.rates[date] = rate
                }
            }
        }
    }
}

last30Days is a constant, of type [Date], storing a Date for every day in the last 30 days. self.currency is a enum type value passed to this view controller by another view controller. self.currency.currencyCode is a computed property that returns the currency code of the currency represented by the enum, e.g. "GBP". Basically, getRate is making 30 requests and adding the responses values to the rates dictionary.

What I want to do is after all 30 requests have been responded without an error, a chart is plotted according to the exchange rates. Currently, I check if the dictionary has 30 entries. If it does, refresh the chart:

var rates: [Date: Double] = [:]
    {
    didSet {
        if rates.count == 30 {
            refreshCharts()
        }
    }
}

This worked fine until I want to add a "refresh" button. When the user refreshes, I want to make the same 30 requests again. If they all succeed without error, populate rates with the new values. If one or more error occurred, I want to keep the value of rates unchanged i.e. keep the old data.

To do this, I must know when all 30 requests have been responded to and whether is any error. I can't use the if rates.count == 30 trick anymore because when the user refreshes, rates is already populated with old values. I can't set rates to empty at the beginning because that will lose the old data, which is needed to be displayed when there is any error. I can't guarantee that will be no errors at the start of getRate.

Basically, how can I know when all 30 requests are responded to and whether any errors occurred?

Upvotes: 0

Views: 1107

Answers (1)

Xavier Lowmiller
Xavier Lowmiller

Reputation: 1401

You can use a DispatchGroup:

let group = DispatchGroup()
var errors: [Error] = []

for date in last30Days {
    group.enter()

    // ...

    Alamofire.request(url).responseString {
        defer { group.leave() }
        guard let error = response.error else {
            errors.append(error)
            return
        }

        // ...
    }
}

group.notify(queue: .main) { 
    // All requests are finished now
}

Please note that the errors array will not be thread safe (same as your dictionary).

Edit: For thread safety, you can dispatch your updates on a queue to ensure the variables aren't modified from different threads at once.

let queue = DispatchQueue(label: "queue")
var errors: [Error] = []
for date in last30Days {
    // ...
    queue.async {
        errors.append(error)
    }
    // ...
}

You can do the same for your dictionary

Upvotes: 5

Related Questions