Reputation: 271660
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
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