How to Use Dispatchgroup/Gcd to Execute Functions Sequentially in Swift

Swift 4 async call with for loop execute in order using DispatchGroup, DispatchQueue and DispatchSemaphore

You can put the whole loop in a block instead of putting just the download function in a block:

dispatchQueue.async {
for c in self.categories {
if let id = c.categoryId {
self.downloadProductsByCategory(categoryId: id) { success, data in
if success, let products = data {
self.products.append(products)
}

dispatchSemaphore.signal()
}
dispatchSemaphore.wait()
}
}
}

You can simplify your code by using compactMap to unwrap your product ids:

for id in self.categories.compactMap({$0.categoryId}) { ... }

Swift / How to use dispatch_group with multiple called web service?

The Problem

As you stated, calls to dispatch_group_enter and dispatch_group_leave must be balanced. Here, you are unable to balance them because the function that performs the actual real-time fetching only calls leave.

Method 1 - All calls on the group

If you take no issue with myFirebaseFunction always performing its work on that dispatch group, then you could put both enter and leave inside there, perhaps with a beginHandler and completionHandler:

func loadStuff() {
myFirebaseFunction(beginHandler: {
dispatch_group_enter(group)
dispatch_group_notify(group, dispatch_get_main_queue()) {
print("done")
}
}, completionHandler: { dispatch_group_leave(group) })

}

func myFirebaseFunction(beginHandler: () -> (), completionHandler: () -> ()) {

let usersRef = firebase.child("likes")
usersRef.observeEventType(.Value, withBlock: { snapshot in

beginHandler()
if snapshot.exists() {
let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])

for item in sorted {

dict.append(item as! NSDictionary)
}
}
completionHandler()
})
}

Here, the completion handler would still be set to dispatch_group_leave by loadStuff, but there is also a begin handler that would call dispatch_group_enter and also dispatch_group_notify. The reason notify would need to be called in begin is that we need to ensure that we have already entered the group before we call notify or the notify block will execute immediately if the group is empty.

The block you pass to dispatch_group_notify will only be called exactly once, even if blocks are performed on the group after notify has been called. Because of this, it might be safe for each automatic call to observeEventType to happen on the group. Then anytime outside of these functions that you need to wait for a load to finish, you can just call notify.

Edit: Because notify is called each time beginHandler is called, this method would actually result in the notify block being called every time, so it might not be an ideal choice.

Method 2 - Only first call on group, several methods

If what you really need is for only the first call of observeEventType to use the group, then one option is to have two versions of myFirebaseFunction: one much like the one you already have and one using observeSingleEventOfType. Then load stuff could call both of those functions, only passing dispatch_group_leave as a completion to one of them:

func loadStuff() {
dispatch_group_enter(group)
myInitialFirebaseFunction() {
dispatch_group_leave(group)
}

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

myFirebaseFunction({})
}

func myInitialFirebaseFunction(completionHandler: () -> ()) {

let usersRef = firebase.child("likes")
usersRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
processSnapshot(snapshot)
completionHandler()
})
}

func myFirebaseFunction(completionHandler: () -> ()) {

let usersRef = firebase.child("likes")
usersRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
processSnapshot(snapshot)
completionHandler()
})
}

func processSnapshot(snapshot: FDataSnapshot) {

if snapshot.exists() {
let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])

for item in sorted {
dict.append(item as! NSDictionary)
}
}
}

Method 3 - Only first call on group, no extra methods

Note that because loadStuff in "Method 2" basically loads things from Firebase twice, it might not be as efficient as you'd like. In that case you could instead use a Bool to determine whether leave should be called:

var shouldLeaveGroupOnProcess = false

func loadStuff() {
dispatch_group_enter(group)
shouldLeaveGroupOnProcess = true
myFirebaseFunction() {
if shouldLeaveGroupOnProcess {
shouldLeaveGroupOnProcess = false
dispatch_group_leave(group)
}
}

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

func myFirebaseFunction(completionHandler: () -> ()) {

let usersRef = firebase.child("likes")
usersRef.observeEventType(.Value, withBlock: { snapshot in

if snapshot.exists() {
let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])

for item in sorted {
dict.append(item as! NSDictionary)
}
}
completionHandler()
})
}

Here, even if multiple calls to observeEventType are made during the initial load, leave is guaranteed to be called only once and no crashed should occur.

Swift 3

PS Currently I'm using Swift 2.3, but an upgrade to Swift 3 is planned, so it would be very awesome to receive an answer capable for both.

Dispatch has gotten a complete overhaul in Swift 3 (it is object-oriented), so code that works well on both is not really a thing :)

But the concepts of each of the three methods above are the same. In Swift 3:

  • Create your group with one of the inits of DispatchGroup
  • dispatch_group_enter is now the instance method enter on the group
  • dispatch_group_leave is now the instance method leave on the group
  • dispatch_group_notify is now the instance method notify on the group

Why is there no dispatch_group_sync function for groups in GCD?

Because here async is relative to the thread(Common main tread) that the block is submitted.

You do not need to sync groups to that thread,you just add code to that thread,code is executed one by one.

Besides,with dispatch group.

  • You can let tasks execute one by one if you add those tasks to a serial queue(DISPATCH_QUEUE_SERIAL).
  • You can also let tasks execute concurrent if you add those tasks to a concurrent queue(DISPATCH_QUEUE_CONCURRENT).

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 DispatchGroup() in Swift 3 to perform a task?

With hint provided in the comment and solution found here: from @vadian, since I am only performing one task, I used a async completion handler:

Second Class:

func checkSpeed(completion: @escaping () -> ())
{
// call other functions and perform task to download file from link

// print out speed of download

nMbps = speedOfDownload
completion()

}

First Class:

let check: SecondClass = SecondClass()

check.checkSpeed {
print("speed = \(check.nMbps)")

}

Now checkSpeed will complete first and speed is assigned the appropriate value.

Swift -is it necessary to call continue when leaving a dispatchGroup

Yes, it is absolutely necessary to call continue, since you want to avoid continuing the execution of the body of your loop.

Calling DispatchGroup.leave does not exit the current scope, you need to call continue to achieve that. leave only affects whatever you are doing with the DispatchGroup - so consequent notify or wait calls.



Related Topics



Leave a reply



Submit