I'm using DispatchGroup in a class that runs on a background thread. Occasionally, I need to update the UI, so I call the following code:
dispatchGroup.notify(queue: .main) {
self.delegate?.moveTo(sender: self, location: location)
self.delegate?.updateLabel(sender: self, item: self.currentItem)
}
Unfortunately, nothing happens. However, if I call the same code, via DispatchQueue.main.async { }, like so:
DispatchQueue.main.async {
self.delegate?.moveTo(sender: self, location: location)
self.delegate?.updateLabel(sender: self, item: self.currentItem)
}
...the delegate call gets made. I was under the impression dispatchGroup.notify(queue: .main) { } is equivalent to DispatchQueue.main.async { }.
Why are these not the same?
Is your dispatchGroup empty (i.e. have no blocks running) at the time when you call notify(queue:)? If not, as documentation states dispatchGroup.notify(queue:)
Schedules a work item to be submitted to a queue when a group of previously submitted block objects have completed.
Which means that your closure will be executed only after the last leave() call, when the group becomes empty. And, of course, enter()s and leave()s must be balanced.
Consider the following example:
let group = DispatchGroup()
group.enter()
someLongRunningTask() {
// completion callback
group.leave()
}
group.enter()
anotherLongRunningTask() {
// completion callback
group.leave()
}
group.notify(queue: .main) {
print("all set")
}
In this example all set will be printed only after two callbacks with group.leave() execute.
On the other hand, DispatchQueue.main.async() immediately submits the block to the target queue but it will not necessarily start right after that – there could be running, for example, async block with the .barrier flag.
Update: Implementation of the example above using DispatchQueue (hopefully, it makes things clear):
let group = DispatchGroup()
group.enter()
someLongRunningTask() {
// completion callback
group.leave()
}
group.enter()
anotherLongRunningTask() {
// completion callback
group.leave()
}
group.wait() // waits synchronously for the submitted work to complete
DispatchQueue.main.async {
print("all set")
}
Related
I need to be able to call a completion block once all the asynchronous functions have completed. However, they don't all have completion blocks. This is my code:
func pauseStream(completion: #escaping () -> ()) {
disconnectFromSession()
model.publishers = []
model.pauseStream() { result in
}
}
disconnectFromSession is an asynchronous function that when completed fires a callback function didDisconnectFromSession in a delegate class.
Setting model.publishers = [] posts a Notification to NotificationCenter that is received by a class which then updates a UI.
Finally model.pauseStream() has a completion block to let me know when it's completed.
What I need to do is once all the asynchronous parts of the code have completed, I want to call the completion() block of my pauseStream function. What's the best way to do this? Unfortunately I can't change them to all have a completion block.
You generally use dispatch groups for this sort of thing. The trick here is that if you need to wait for disconnectFromSession to call its completion handler, then need to have didDisconnectFromSession call leave for the dispatch group.
So create ivar for the dispatch group:
let group = DispatchGroup()
Have your pauseStream use this DispatchGroup to call its completion handler when enter calls are offset by their corresponding leave calls:
func pauseStream(completion: #escaping () -> ()) {
group.enter()
disconnectFromSession() // this will call `leave` in its delegate method
model.publishers = []
group.enter()
model.someAsynchronousMethod() { result in
defer { group.leave() }
...
}
group.notify(queue: .main) {
completion()
}
}
And, your didDisconnectFromSession, would call that corresponding leave:
func didDisconnectFromSession(...) {
group.leave()
...
}
I'm using DispatchGroup to perform a task, but group.notify is being called before the task is completed.
My code:
let group = DispatchGroup()
let queueImage = DispatchQueue(label: "com.image")
let queueVideo = DispatchQueue(label: "com.video")
queueImage.async(group: group) {
sleep(2)
print("image")
}
queueVideo.async(group: group) {
sleep(3)
print("video")
}
group.notify(queue: .main) {
print("all finished.")
}
Logs:
all finish.
image
video
Update: The question above actually runs correctly as is (as rmaddy pointed out!)
I'm saving this wrong answer below in case others get confused about DispatchQueue's async(group:) methods behavior, since Apple's swift doc on it is currently lousy.
The group's enter() needs to be called before each call to async(), and then the group's leave() needs to be called at end of each async() block, but within the block. It's basically like a refcount that when it reaches zero (no enters remaining), then the notify block is called.
let group = DispatchGroup()
let queueImage = DispatchQueue(label: "com.image")
let queueVideo = DispatchQueue(label: "com.video")
group.enter()
queueImage.async(group: group) {
sleep(2)
print("image")
group.leave()
}
group.enter()
queueVideo.async(group: group) {
sleep(3)
print("video")
group.leave()
}
group.notify(queue: .main) {
print("all finished.")
}
Generic answer : (Swift 5)
let yourDispatchGroup = DispatchGroup()
yourDispatchGroup.enter()
task1FunctionCall {
yourDispatchGroup.leave() //task 1 complete
}
yourDispatchGroup.enter()
task2FunctionCall {
yourDispatchGroup.leave() //task 2 complete
}
.. ..
yourDispatchGroup.enter()
tasknFunctionCall {
yourDispatchGroup.leave() //task n complete
}
dispatchGroup.notify(queue: .main) {
//This is invoked when all the tasks in the group is completed.
}
If your DispatchGroup is a lazy var, try to not call the notify method inside the initialization code block.
lazy var dispatchGroup: DispatchGroup = {
let dispatchGroup = DispatchGroup()
// not call here dispatchGroup.notify(...
return dispatchGroup
}()
You need to call all the enter methods before the notify method:
dispatchGroup.enter()
dispatchQueue.async(group: dispatchGroup) {
// ...
self.dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main) {
print("all finished.")
}
Trying to get return reply objects from a network call. The session is a class that is using the star scream API. I just can't seem to get this to work. It's only printing out 1 set of results which is the from the first id. What am I missing here?
let myGroup = DispatchGroup()
for i in 0 ..< marketIds.count {
myGroup.enter()
self.session.retrieve(withMethod: MarketKeys.key, withParameters: [MarketKeys.id: marketIds[i]], completion: { (results, error) in
print("results \n")
print(results!)
myGroup.leave()
})
}
myGroup.notify(queue:.main) {
print("Done")
}
This article gives you a quick reference guide to simple DispatchGroup use.
An example:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
longRunningFunction { dispatchGroup.leave() }
dispatchGroup.enter()
longRunningFunctionTwo { dispatchGroup.leave() }
dispatchGroup.notify(queue: .main) {
print("Both functions complete 👍")
}
The notify function is called when all items in the queue have been processed and allows you to react to this accordingly. So the example above will run two long running tasks and then will output "Both functions complete 👍"
Add this so notification to group can be sent
myGroup.notify(queue: .main) {
print("Both functions complete 👍")
}
Say I want a few asynchronous tasks to complete before running some completion block. I decide to make a function that uses a Dispatch Group to make sure those things get done before continuing, something like this:
func doStuff() {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
Do asynchronous task 1 {
dispatchGroup.leave()
}
dispatchGroup.enter()
Do asynchronous task 2 {
dispatchGroup.leave()
}
}
Would the code above behave the exact same as the following code?
func doStuff() {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.enter()
Do asynchronous task 1 {
dispatchGroup.leave()
}
Do asynchronous task 2 {
dispatchGroup.leave()
}
}
In other words, do I need to put the Dispatch Group enters right next to the task I want to complete or do they work more like a counter where I can just put all of them at the start corresponding to the number of tasks I want to complete?
In my application I have a requirement to get the response before loading the tableview. I need to call around 20 API's at same time. And each API data need to show each 1 cell in tableview.
I need to call them in Viewdidload method which calls before tableview methods.
Can anyone guide or provide some useful example to do this?
My suggestion is to use GCD's groups for that.
let backgroundQueue = DispatchQueue.global(attributes: .qosDefault)
let group = DispatchGroup()
var dataForTable:[String] = []
for number in 0..<n {
group.enter()
// Do your request with async callback, append data and leave GCD group.
backgroundQueue.async(group: group, execute: {
let newData = String()
dataForTable.append(newData)
group.leave()
})
}
group.notify(queue: DispatchQueue.main, execute: {
print("All requests data")
self.tableViewData = dataForTable
self.tableView.reloadData()
})
You should use dispatch groups like this:
let group = DispatchGroup()
group.enter()
networkCall1 {
// response received
group.leave()
}
group.enter()
networkCall2 {
// response received
group.leave()
}
group.notify(queue: DispatchQueue.main, execute: {
// this will be notified only when there is no one left in the group
})
Before a network call you enter a group. When you receive a response you leave the group and when there is no one left in the group, group.notify block will execute.
This is just a simple explanation, you should read more about it to fully understand how it works.