How to Use Dispatch Groups to Wait to Call Multiple Functions That Depend on Different Data

How can you use Dispatch Groups to wait to call multiple functions that depend on different data?

To achieve that with dispatch groups alone you would need three
dispatch groups which are entered and left accordingly:

let abGroup = DispatchGroup()
let bcGroup = DispatchGroup()
let acGroup = DispatchGroup()

abGroup.enter()
abGroup.enter()
bcGroup.enter()
bcGroup.enter()
acGroup.enter()
acGroup.enter()

// When a is updated:
abGroup.leave()
acGroup.leave()

// When b is updated:
abGroup.leave()
bcGroup.leave()

// When c is updated:
acGroup.leave()
bcGroup.leave()

Then you can wait for the completion of each group independently

abGroup.notify(queue: .main) {
// Do something with a and b
}
bcGroup.notify(queue: .main) {
// Do something with b and c
}
acGroup.notify(queue: .main) {
// Do something with a and c
}

However, this does not scale well with more tasks and dependencies.

The better approach is to add Operations to an
OperationQueue, that allows to add arbitrary dependencies:

let queue = OperationQueue()

let updateA = BlockOperation {
// ...
}
queue.addOperation(updateA)

let updateB = BlockOperation {
// ...
}
queue.addOperation(updateB)

let updateC = BlockOperation {
// ...
}
queue.addOperation(updateC)

let doSomethingWithAandB = BlockOperation {
// ...
}
doSomethingWithAandB.addDependency(updateA)
doSomethingWithAandB.addDependency(updateB)
queue.addOperation(doSomethingWithAandB)

let doSomethingWithBandC = BlockOperation {
// ...
}
doSomethingWithBandC.addDependency(updateB)
doSomethingWithBandC.addDependency(updateC)
queue.addOperation(doSomethingWithBandC)

let doSomethingWithAandC = BlockOperation {
// ...
}
doSomethingWithAandC.addDependency(updateA)
doSomethingWithAandC.addDependency(updateC)
queue.addOperation(doSomethingWithAandC)

For asynchronous request with completion handlers you can use a
(local) dispatch group inside each block operation to wait for the
completion.

Here is a self-contained example:

import Foundation

var a: String?
var b: String?
var c: String?

let queue = OperationQueue()

let updateA = BlockOperation {
let group = DispatchGroup()
group.enter()
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0, execute: {
a = "A"
group.leave()
})
group.wait()
print("updateA done")
}
queue.addOperation(updateA)

let updateB = BlockOperation {
let group = DispatchGroup()
group.enter()
DispatchQueue.global().asyncAfter(deadline: .now() + 2.0, execute: {
b = "B"
group.leave()
})
group.wait()
print("updateB done")
}
queue.addOperation(updateB)

let updateC = BlockOperation {
let group = DispatchGroup()
group.enter()
DispatchQueue.global().asyncAfter(deadline: .now() + 3.0, execute: {
c = "C"
group.leave()
})
group.wait()
print("updateC done")
}
queue.addOperation(updateC)

let doSomethingWithAandB = BlockOperation {
print("a=", a!, "b=", b!)
}
doSomethingWithAandB.addDependency(updateA)
doSomethingWithAandB.addDependency(updateB)
queue.addOperation(doSomethingWithAandB)

let doSomethingWithAandC = BlockOperation {
print("a=", a!, "c=", c!)
}
doSomethingWithAandC.addDependency(updateA)
doSomethingWithAandC.addDependency(updateC)
queue.addOperation(doSomethingWithAandC)

let doSomethingWithBandC = BlockOperation {
print("b=", b!, "c=", c!)
}
doSomethingWithBandC.addDependency(updateB)
doSomethingWithBandC.addDependency(updateC)
queue.addOperation(doSomethingWithBandC)

queue.waitUntilAllOperationsAreFinished()

Output:


updateA done
updateB done
a= A b= B
updateC done
a= A c= C
b= B c= C

Several tasks inside a DispatchGroup. Will they run in order?

To stick with DispatchGroup while preserving the desired asynchronous nature and the expected ordering, make your array an array of optionals and populate it in whatever order the tasks complete:

var processedData: [SomeType?] = Array(repeating: nil, count: N)
let dispatchGroup = DispatchGroup()
for idx in 0..<N {
dispatchGroup.enter()
startSomeAsyncTaskXYZ { (data, error) in
// Ensure we always .leave() after we're done
// handling the completion of the task
defer { dispatchGroup.leave() }

guard let data = data,
error == nil else {
// TODO: Actual error handling
return
}

// This needs to be .sync now (not .async) to ensure
// the deferred dispatchGroup.leave() is not called
// until *after* we've updated the array
DispatchQueue.main.sync {
processedData[idx] = SomeType(data: data)
}
}
}
dispatchGroup.notify(queue: .main) {
// update UI
}

Using dispatch queues with multiple async calls

When the second async call depends on the first, but no other first data calls from any other objects within ref, then this should work:

func Foo(completion: ()->()) {
let dispatchGroup = DispatchGroup()

for doc in documents {
dispatchGroup.enter()
ref.getDocument(completion: { (snapshot, error) in
userCountRef.getDocuments(completion: { (snap, error) in
let Object = Object(init with snapshot data)
dispatchGroup.leave()
}
}
}

// .main here is the main DispatchQueue. Can be any queue desired
dispatchGroup.notify(.main) {
completion()
}
}

This notify will ensure that all the enter's have been matched by a closing leave before calling your completion. See more at the apple docs here.

Adding dependency of one BlockOperation on another is not working properly in swift

You are way overthinking this. Just call the second API from the completion handler of the first API. No operations, no dispatch groups, no nothing.

self.getFirstDataFromApi {
self.getSecondDataFromApi {
// call the completion handler
}
}

As for why your code didn't work, it's because you didn't do what the linked answer said to do!

How can you use Dispatch Groups to wait to call multiple functions that depend on different data?

It said to do this:

getSecondDataOperation.addDependency(getFirstDataOperation)
queue.addOperation(getFirstDataOperation)
queue.addOperation(getSecondDataOperation)

That isn't what you did. You did this:

queue.addOperation(getFirstDataOperation)
queue.addOperation(getSecondDataOperation)
getSecondDataOperation.addDependency(getFirstDataOperation) // too late

(However, that post, while ingenious, is not what I would do in this situation. If I wanted to sequentialize download operations, I would use the technique described here: https://fluffy.es/download-files-sequentially/. Or, in iOS 13, I'd use the Combine framework, as I describe here: https://stackoverflow.com/a/59889993/341994.)

Waiting until two async blocks are executed before starting another block

Use dispatch groups: see here for an example, "Waiting on Groups of Queued Tasks" in the "Dispatch Queues" chapter of Apple's iOS Developer Library's Concurrency Programming Guide

Your example could look something like this:

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block1
NSLog(@"Block1");
[NSThread sleepForTimeInterval:5.0];
NSLog(@"Block1 End");
});


dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block2
NSLog(@"Block2");
[NSThread sleepForTimeInterval:8.0];
NSLog(@"Block2 End");
});

dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block3
NSLog(@"Block3");
});

// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group);

and could produce output like this:

2012-08-11 16:10:18.049 Dispatch[11858:1e03] Block1
2012-08-11 16:10:18.052 Dispatch[11858:1d03] Block2
2012-08-11 16:10:23.051 Dispatch[11858:1e03] Block1 End
2012-08-11 16:10:26.053 Dispatch[11858:1d03] Block2 End
2012-08-11 16:10:26.054 Dispatch[11858:1d03] Block3

How to wait until data from network call comes and only then return value of a function #Swift

I think that you were on the right path when attempting to use a completion block, just didn't do it correctly.

func loadList(at page: Int, completion: @escaping ((Error?, Bool, [Beer]?) -> Void)) {
//MARK: - Checks is URL is valid + pagination
guard let url = URL(string: "https://api.punkapi.com/v2/beers?page=\(page)&per_page=25") else {
print("Invalid URL")
completion(nil, false, nil)
return
}
//MARK: - Creating URLSession DataTask
let task = URLSession.shared.dataTask(with: url){ data, response, error in
//MARK: - Handling no erros came
if let error = error {
completion(error, false, nil)
print(error!)
return
}
//MARK: - Handling data came
guard let data = data, let beers = try? JSONDecoder().decode([Beer].self, from: data) else {
completion(nil, false, nil)
return
}
completion(nil, true, beers)
}
task.resume()
}

This is the loadList function, which now has a completion parameter that will have three parameters, respectively the optional Error, the Bool value representing success or failure of obtaining the data, and the actual [Beers] array, containing the data (if any was retrieved).

Here's how you would now call the function:

service.loadList(at: page) { error, success, beers in
if let error = error {
// Handle the error here
return
}

if success, let beers = beers {
// Data was correctly retrieved - and safely unwrapped for good measure, do what you need with it
// Example:
loader.stopLoading()
self.datasource = beers
self.tableView.reloadData()
}
}

Bear in mind the fact that the completion is being executed asynchronously, without stopping the execution of the rest of your app.
Also, you should decide wether you want to handle the error directly inside the loadList function or inside the closure, and possibly remove the Error parameter if you handle it inside the function.
The same goes for the other parameters: you can decide to only have a closure that only has a [Beer] parameter and only call the closure if the data is correctly retrieved and converted.

Checking for multiple asynchronous responses from Alamofire and Swift

Better than that looping process, which would block the thread, you could use dispatch group to keep track of when the requests were done. So "enter" the group before issuing each of the requests, "leave" the group when the request is done, and set up a "notify" block/closure that will be called when all of the group's tasks are done.

For example, in Swift 3:

let group = DispatchGroup()

group.enter()
retrieveDataFromURL(url1, parameters: firstParameters) {
group.leave()
}

group.enter()
retrieveDataFromURL(url2, parameters: secondParameters) {
group.leave()
}

group.notify(queue: .main) {
print("both requests done")
}

Or, in Swift 2:

let group = dispatch_group_create()

dispatch_group_enter(group)
retrieveDataFromURL(url1, parameters: firstParameters) {
dispatch_group_leave(group)
}

dispatch_group_enter(group)
retrieveDataFromURL(url2, parameters: secondParameters) {
dispatch_group_leave(group)
}

dispatch_group_notify(group, dispatch_get_main_queue()) {
print("both requests done")
}

The other approach is to wrap these requests within an asynchronous NSOperation subclass (making them cancelable, giving you control over constraining the degree of concurrency, etc.), but that's more complicated, so you might want to start with dispatch groups as shown above.

Swift: Sending network request one after another


A simple network layer utilizing Combine

This answer is heavily influenced by this post. I removed unnecessary code where possible and added code where needed to compose a working playground.

Api unrelated layer parts

import Foundation
import Combine

struct Agent {

func run<T: Decodable>(_ request: URLRequest, _ decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<T, Error> {
return URLSession.shared
.dataTaskPublisher(for: request)
.tryMap { result -> T in
return try decoder.decode(T.self, from: result.data)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}

Api related layer parts

enum GithubAPI {
static let agent = Agent()
static let base = URL(string: "https://api.github.com")!
}

extension GithubAPI {

static func repos(username: String) -> AnyPublisher<[Repository], Error> {
let request = URLRequest(url: base.appendingPathComponent("users/\(username)/repos"))
return agent.run(request)
}

struct Repository: Codable {
let node_id: String
let name: String
}

static func issues(repo: String, owner: String) -> AnyPublisher<[Issue], Error> {
let request = URLRequest(url: base.appendingPathComponent("repos/\(owner)/\(repo)/issues"))
return agent.run(request)
}

struct Issue: Codable {
let url: URL
}

}

Actual clean and swifty code you are looking for

Note that issue request depends on repo request. Actual requests are triggered with sink call.

let user = "Alamofire"
let repos = GithubAPI.repos(username: user)
let firstRepo = repos.compactMap { $0.first }
let issues = firstRepo.flatMap { repo in
GithubAPI.issues(repo: repo.name, owner: user)
}
let token = issues.sink(receiveCompletion: { (_) in }) { (issues) in
print(issues)
}


Related Topics



Leave a reply



Submit