I have a few asynchronous, network tasks that I need to perform on my app. Let's say I have 3 resources that I need to fetch from a server, call them A
, B
, and C
. Let's say I have to finish fetching resource A
first before fetching either B
or C
. Sometimes, I'd want to fetch B
first, other times C
first.
Right now, I just have a long-chained closure like so:
func fetchA() {
AFNetworking.get(completionHandler: {
self.fetchB()
self.fetchC()
})
}
This works for now, but the obvious limitation is I've hard-coded the order of execution into the completion handler of fetchA
. Now, say I want to only fetchC
after fetchB
has finished in that completion handler, I'd have to go change my implementation for fetchB
...
Essentially, I'd like to know if there's some magic way to do something like:
let orderedAsync = [fetchA, fetchB, fetchC]
orderedAsync.executeInOrder()
where fetchA
, fetchB
, and fetchC
are all async functions, but fetchB
won't execute until fetchA
has finished and so on. Thanks!
You can use a serial DispatchQueue
mixed with DispatchGroup.enter()
, DispatchGroup.leave()
and DispatchGroup.modify()
which will ensure that only one execution block will run at a time.
let serialQueue = DispatchQueue(label: "serialQueue")
let group = DispatchGroup()
group.enter()
serialQueue.async{ //call this whenever you need to add a new work item to your queue
fetchA{
//in the completion handler call
group.leave()
}
}
serialQueue.async{
group.wait()
group.enter()
fetchB{
//in the completion handler call
group.leave()
}
}
serialQueue.async{
group.wait()
group.enter()
fetchC{
group.leave()
}
}
Or if you are allowed to use a 3rd party library, use PromiseKit
, it makes handling and especially chaining async methods way easier than anything GCD
provides. See the official GitHub page for more info.
You can wrap an async method with a completion handler in a Promise and chain them together like this:
Promise.wrap(fetchA(completion:$0)).then{ valueA->Promise<typeOfValueB> in
return Promise.wrap(fetchB(completion:$0)
}.then{ valueB in
}.catch{ error in
//handle error
}
Also, all errors are propagated through your promises.
What you are describing is already the default behavior for a serial queue:
let orderedAsync = [fetchA, fetchB, fetchC]
let queue = DispatchQueue(label: "fetchQueue")
for task in orderedAsync{
queue.async(execute: task)
}
print("all enqueued")
"all enqueued" will print immediately, and each task will wait for the previous one to finish before it starts.
FYI, if you added attributes: .concurrent
to your DispatchQueue
initialization, then they wouldn't be guaranteed to execute in order. But even then you can use the .barrier
flag when you want things to execute in order.
In other words, this would also fulfill your requirements:
let queue = DispatchQueue(label: "fetchQueue", attributes: .concurrent)
for task in orderedAsync{
queue.async(flags: .barrier, execute: task)
}