I recently had this problem: at the start of the app there's a call to fetch some reference data, on which other calls depend, but it shouldn't hinder the app launch itself, nor anything else that doesn't depend on it. So, after several approaches, I decided to use DispatchGroups
.
First, a struct
to abstract a DispatchQueue
and a DispatchGroup
:
struct Queue {
let group = DispatchGroup()
let queue: DispatchQueue
init(label: String) {
queue = DispatchQueue(label: label)
}
}
This class should have a way to add a closure to the queue, that won't require waiting, basically just abstracting async(execute:)
:
func add(_ closure: () -> Void) {
queue.async(execute: closure)
}
And also a way to add a closure to the queue, that will require waiting:
func addAndWait(_ closure: () -> Void) {
// Dispatch on our queue
queue.async {
self.group.enter()
// Fire up the closure
closure()
// And wait for the leave call
_ = self.group.wait(timeout: DispatchTime.distantFuture)
}
}
func advance() { // Any form of synonym for continue, I guess
group.leave()
}
Using it feels rather straightforward:
let queue = Queue(label: "com.rolandleth.demoapp.loadQueue")
queue.addAndWait {
API.fetchReferenceData { _ in
print("fetched 1")
queue.advance()
}
}
queue.add {
API.fetchResource1 { _ in
print("fetched 2")
}
}
queue.addAndWait {
self.timeConsumingJob()
print("1")
queue.advance()
}
queue.add {
print("2")
}
queue.addAndWait {
API.fetchResource2 { _ in
print("fetched 3")
queue.advance()
}
}
queue.add {
self.jobDependantOnResource2()
print("3")
}
This would print out:
fetched 1
fetched 2
1
2
fetched 3
3
// Or if timeConsumingJob() finishes faster than fetchResource1():
fetched 1
1
2
fetched 2
fetched 3
3
I'm sure this isn't perfect, and won't work for everyone, but it should serve some purpose, nonetheless. DispatchSemaphore
would have also worked, and so would have subclassing NSOperationQueue
, but I found this shorter and easier to abstract. One could improve this by passing a custom timeout
and a custom DispatchQoS
, for example.