Queueing up async jobs

:  ~ 1 min read

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.