You said:
Now the question arises if I want to get below 2 scenarios -
- I want operation2 to start after my operation1 finishes, and operation3 to start after operation2 finishes. So that all operations are completed in combined (6.0+4.0+2.0) 12.0 seconds.
First, this is a pattern generally to be avoided with network requests because you’re going to magnify network latency effects. You would only use this pattern where absolutely needed, e.g. if request 1 was a “sign in” operation; or, you needed something returned in the first request in order to prepare the subsequent request).
Often we’d do something simple, such as initiating the subsequent request in the completion handler of the first. Or, if you wanted a more flexible set of dependencies from a series of requests, you adopt a pattern that doesn’t use dispatch queues, e.g. you might create a custom, asynchronous Operation
subclass that only completes when the network request is done (see point 3 in https://stackoverflow.com/a/57247869/1271826). Or if targeting recent OS versions, you might use Combine. There are a whole bunch of alternatives here. But you can’t just start a bunch of asynchronous tasks and have them run sequentially without one of these sorts of patterns.
- I want all operations to start simultaneously, but completions to trigger in order they were entered in queue. So that all operations are completed in combined 6.0 seconds.
The whole idea of concurrent patterns is that you shouldn’t care about the order that they finish. So, use a structure that is not order-dependent, and then you can store the results as they come in. And use dispatch group to know when they’re all done.
But one thing at a time. First, how do you know when a bunch of concurrent requests are done? Dispatch groups. For example:
let group = DispatchGroup()
group.enter()
queue.asyncAfter(deadline: .now() + 6) {
defer { group.leave() }
print("Image Download 1 Done")
}
group.enter()
queue.asyncAfter(deadline: .now() + 4) {
defer { group.leave() }
print("Image Download 2 Done")
}
group.enter()
queue.asyncAfter(deadline: .now() + 2) {
defer { group.leave() }
print("Image Download 3 Done")
}
group.notify(queue: .main) {
// all three are done
}
Now, how do you take these requests, store the results, and retrieve them in the original order when you’re all done? First, create some structure that is independent of the order of the tasks. For example, let’s say you were downloading a bunch of images from URLs, then create a dictionary.
var images: [URL: UIImage] = [:]
Now fire off the requests concurrently:
for url in urls {
group.enter()
downloadImage(url) { result in
defer { group.leave() }
// do something with the result, e.g. store it in our `images` dictionary
switch result {
case .failure(let error): print(error)
case .success(let image): images[url] = image
}
}
}
// this will be called on main queue when they’re all done
group.notify(queue: .main) {
// if you want to pull them in the original order, just iterate through your array
for url in urls {
if let image = images[url] {
print("image \(url) has \(image.size)")
}
}
}
By the way, the above is using the following method to retrieve the images:
enum DownloadError: Error {
case unknown(Data?, URLResponse?)
}
@discardableResult
func downloadImage(_ url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let responseData = data,
let image = UIImage(data: responseData),
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
error == nil
else {
DispatchQueue.main.async {
completion(.failure(error ?? DownloadError.unknown(data, response)))
}
return
}
DispatchQueue.main.async {
completion(.success(image))
}
}
task.resume()
return task
}
The details here are not relevant. The key observation is that you should embrace concurrent patterns for determining when tasks are done (using dispatch groups, for example) and retrieving the results (store results in an unordered structure and access them in a manner that honors the order you intended).