Nested dispatch queues not firing inner dispatch queue - ios

I have a pair of nested dispatch queues. For some reason the code inside the second one never gets fired:
dispatchGroupOne.notify(queue: .main) { [weak self] in
// Gets here
self?.dispatchGroupTwo.notify(queue: .main) {
// Doesn't get here
}
}
Even if I don't enter/leave dispatchGroupTwo, it just never will get fired. Why is this? It works when it's not nested:
dispatchGroupOne.notify(queue: .main) {
// Gets here
}
dispatchGroupTwo.notify(queue: .main) {
// Gets here
}
However, I want to specifically perform code only after both of the dispatch groups have been fired.

Without nesting you register the listen separately so every group will act according to it's submitted tasks ,while when you nest them , then the notify of the inner group will depend on the outer one plus whether or not the it's (the inner group) tasks ended/working when it's notify is added upon triggering the outer notify
let dispatchGroupOne = DispatchGroup()
let dispatchGroupTwo = DispatchGroup()
let dispatchGroupThird = DispatchGroup()
dispatchGroupThird.enter()
dispatchGroupOne.notify(queue: .main) {
// Gets here
dispatchGroupThird.leave()
}
dispatchGroupThird.enter()
dispatchGroupTwo.notify(queue: .main) {
// Gets here
dispatchGroupThird.leave()
}
dispatchGroupThird.notify(queue: .main) {
// All groups are done
}

Related

Update collection view after computing view models in background

I receive data items from my API, on a specific serial queue, that I use to calculate the view models to display in a collection view. The simplified code is:
var viewModels: [ViewModel] = [] // read and written on main queue
var _viewModels: [ViewModel] = [] // written in background, read in main queue
func didReceive(items: [Item]) {
// called on the same background serial queue
_viewModels = items.map { ViewModel($0) }
DispatchQueue.main.async {
self.viewModels = self._viewModels
self.updateCollectionView {
// UI update has finished
}
}
}
didReceive can be called at any time.
_viewModels is written always on the same background queue. After that I switch to main queue to cache the computed view models and use it in data source for displaying.
Can self.viewModels = self._viewModels lead to crashes? Do I have to use some kind of locking mechanism? If yes, how does it work when main thread is involved?
Crashes not necessarily, but this can lead to inconsistency. As _viewModels is declared outside the scope of didReceive, it is a shared resource that can be modified each time you enter didReceive. After dispatching this to the main thread, some other thread can enter in didReceive and change your value. So when you assign it to self.viewModels, you would get the "second" value.
I believe that declaring _viewModels inside didReceive would fix your issue, as you would have a reference for each run of didReceive:
var viewModels: [ViewModel] = [] // read and written on main queue
func didReceive(items: [Item]) {
// called on the same background serial queue
let _viewModels = items.map { ViewModel($0) }
DispatchQueue.main.async {
self.viewModels = _viewModels
self.updateCollectionView {
// UI update has finished
}
}
}
If you are not allowed to do this, try taking a look at Actors:
https://www.avanderlee.com/swift/actors/

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

I have multiple api's in a controller and after successful response I have to reload the UITableView.
For now I started with two api having second api dependency on first one using BlockOperation and DispatchGroup in it.
First in viewDidLoad:
getDataFromAllApis {
self.tableView.reloadData()
}
Then I added the method:
func getDataFromAllApis(completion: #escaping (() -> Void)) {
let queue = OperationQueue()
let getFirstDataOperation = BlockOperation {
let group = DispatchGroup()
group.enter()
self.getFirstDataFromApi {
group.leave()
}
group.wait()
}
queue.addOperation(getFirstDataOperation)
let getSecondDataOperation = BlockOperation {
let group = DispatchGroup()
group.enter()
self.getSecondDataFromApi {
group.leave()
}
group.notify(queue: .main) {
completion()
}
}
queue.addOperation(getSecondDataOperation)
getSecondDataOperation.addDependency(getFirstDataOperation)
}
The problem that I am facing here is getSecondDataOperation executes first and returns to the tableview reload part.
Am I missing something here or there can be a different approach for it? Any help will be appreciated.
I have tried going through this post :
How can you use Dispatch Groups to wait to call multiple functions that depend on different data?
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.)

In Xcode 9 / Swift 4 Google APIs Client Library for Objective-C for REST: threading notification not working

In Xcode 9 / Swift 4 using Google APIs Client Library for Objective-C for REST: why does service.executeQuery return thread completion notification before the thread completes?
I have been trying various ways but I am stuck with the following code where the notification is returned before the thread completes. See below the code, the actual output and what I would expect to see (notification comes once the thread has complete).
What am I doing wrong?
Thanks
func myFunctionTest () {
let workItem = DispatchWorkItem {
self.service.executeQuery(query,
delegate: self,
didFinish: #selector(self.displayResultWithTicket2b(ticket:finishedWithObject:error:))
)
}
let group = DispatchGroup()
group.enter()
group.notify(queue: service.callbackQueue) {
print("************************** NOTIFY MAIN THREAD *************************************")
}
service.callbackQueue.async(group: group) {
workItem.perform()
}
group.leave()
}
#objc func displayResultWithTicket2b(ticket : GTLRServiceTicket,
finishedWithObject messagesResponse : GTLRGmail_ListMessagesResponse,
error : NSError?) {
//some code to run here
print("************************** 02.displayResultWithTicket2b ***************************")
}
Output
************************** NOTIFY MAIN THREAD *************************************
************************** 02.displayResultWithTicket2b ***************************
What I would expect = Thread notification comes when thread has completed
************************** 02.displayResultWithTicket2b ***************************
************************** NOTIFY MAIN THREAD *************************************
The problem is that you're dealing with an asynchronous API and you're calling leave when you're done submitting the request. The leave() call has to be inside the completion handler or selector method of your executeQuery call. If you're going to stick with this selector based approach, you're going to have to save the dispatch group in some property and then have displayResultWithTicket2b call leave.
It would be much easier if you used the block/closure completion handler based rendition of the executeQuery API, instead of the selector-based API. Then you could just move the leave into the block/closure completion handler and you'd be done. If you use the block based implementation, not only does it eliminate the need to save the dispatch group in some property, but it probably eliminates the need for the group at all.
Also, the callback queue presumably isn't designed for you to add your own tasks. It's a queue that the library will use the for its callbacks (the queue on which completion blocks and/or delegate methods will be run). Just call executeQuery and the library takes care of running the callbacks on that queue. And no DispatchWorkItem is needed:
session.executeQuery(query) { ticket, object, error in
// do whatever you need here; this runs on the callback queue
DispatchQueue.main.async {
// when you need to update model/UI, do that on the main queue
}
}
The only time I'd use a dispatch group would be if I was performing a series of queries and needed to know when they were all done:
let group = DispatchGroup()
for query in queries {
group.enter()
session.executeQuery(query) { ticket, object, error in
defer { group.leave() }
// do whatever you need here; this runs on the callback queue
}
}
group.notify(queue: .main) {
// do something when done; this runs on the main queue
}

Swift threading multiple tasks?

I have this label that is supposed to display a username. Now, I have done quite a bit of IOS developing, however threading is still a bit unclear to me. How would I make sure this code finishes:
User(name: "", email: "", _id: "").getCurrentUser(userId: userId)
Before this gets excecuted?:
self.nameLabel.text = currentUser.name
I have been fumbling with DispatchQueue but I can't seem to figure it out...
Thx in advance!
You can use DispatchGroups to do this, as one solution. Here is an example:
// create a dispatch group
let group = DispatchGroup()
// go "into that group" starting it
group.enter()
// setup what happens when the group is done
group.notify(queue: .main) {
self.nameLabel.text = currentUser.name
}
// go to the async main queue and do primatry work.
DispatchQueue.main.async {
User(name: "", email: "", _id: "").getCurrentUser(userId: userId)
group.leave()
}
Just send a notification in your getCurrentUser() method and add an observer in your UIViewController to update the label.
public extension Notification.Name {
static let userLoaded = Notification.Name("NameSpace.userLoaded")
}
let notification = Notification(name: .userLoaded, object: user, userInfo: nil)
NotificationCenter.default.post(notification)
And in your UIViewController:
NotificationCenter.default.addObserver(
self,
selector: #selector(self.showUser(_:)),
name: .userLoaded,
object: nil)
func showUser(_ notification: NSNotification) {
guard let user = notification.object as? User,
notification.name == .userLoaded else {
return
}
currentUser = user
DispatchQueue.main.async {
self.nameLabel.text = self.currentUser.name
}
}
You have to differentiate synchronous from asynchronous tasks.
Typically, a synchronous task is a task blocking the execution of the program. The next task will not be executed until the previous one finishes.
An asynchronous task is the opposite. Once it is started, the execution passes to the next instruction and you get typically the results from this task with delegating or blocks.
So without more indication, we can't know what exactly getCurrentUser(:) do...
According to Apple :
DispatchQueue manages the execution of work items. Each work item submitted to a queue is processed on a pool of threads managed by the system.
It is not necessarily executing work items on background threads. It is just a structure allowing you to execute synchronously or asynchronously work items on queues (it could be the main queue or another one).

How does a serial queue/private dispatch queue know when a task is complete?

(Perhaps answered by How does a serial dispatch queue guarantee resource protection? but I don't understand how)
Question
How does gcd know when an asynchronous task (e.g. network task) is finished? Should I be using dispatch_retain and dispatch_release for this purpose? Update: I cannot call either of these methods with ARC... What do?
Details
I am interacting with a 3rd party library that does a lot of network access. I have created a wrapper via a small class that basically offers all the methods i need from the 3rd party class, but wraps the calls in dispatch_async(serialQueue) { () -> Void in (where serialQueue is a member of my wrapper class).
I am trying to ensure that each call to the underlying library finishes before the next begins (somehow that's not already implemented in the library).
The serialisation of work on a serial dispatch queue is at the unit of work that is directly submitted to the queue. Once execution reaches the end of the submitted closure (or it returns) then the next unit of work on the queue can be executed.
Importantly, any other asynchronous tasks that may have been started by the closure may still be running (or may not have even started running yet), but they are not considered.
For example, for the following code:
dispatch_async(serialQueue) {
print("Start")
dispatch_async(backgroundQueue) {
functionThatTakes10Seconds()
print("10 seconds later")
}
print("Done 1st")
}
dispatch_async(serialQueue) {
print("Start")
dispatch_async(backgroundQueue) {
functionThatTakes10Seconds()
print("10 seconds later")
}
print("Done 2nd")
}
The output would be something like:
Start
Done 1st
Start
Done 2nd
10 seconds later
10 seconds later
Note that the first 10 second task hasn't completed before the second serial task is dispatched. Now, compare:
dispatch_async(serialQueue) {
print("Start")
dispatch_sync(backgroundQueue) {
functionThatTakes10Seconds()
print("10 seconds later")
}
print("Done 1st")
}
dispatch_async(serialQueue) {
print("Start")
dispatch_sync(backgroundQueue) {
functionThatTakes10Seconds()
print("10 seconds later")
}
print("Done 2nd")
}
The output would be something like:
Start
10 seconds later
Done 1st
Start
10 seconds later
Done 2nd
Note that this time because the 10 second task was dispatched synchronously the serial queue was blocked and the second task didn't start until the first had completed.
In your case, there is a very good chance that the operations you are wrapping are going to dispatch asynchronous tasks themselves (since that is the nature of network operations), so a serial dispatch queue on its own is not enough.
You can use a DispatchGroup to block your serial dispatch queue.
dispatch_async(serialQueue) {
let dg = dispatch_group_create()
dispatch_group_enter(dg)
print("Start")
dispatch_async(backgroundQueue) {
functionThatTakes10Seconds()
print("10 seconds later")
dispatch_group_leave(dg)
}
dispatch_group_wait(dg)
print("Done")
}
This will output
Start
10 seconds later
Done
The dg.wait() blocks the serial queue until the number of dg.leave calls matches the number of dg.enter calls. If you use this technique then you need to be careful to ensure that all possible completion paths for your wrapped operation call dg.leave. There are also variations on dg.wait() that take a timeout parameter.
As mentioned before, DispatchGroup is a very good mechanism for that.
You can use it for synchronous tasks:
let group = DispatchGroup()
DispatchQueue.global().async(group: group) {
syncTask()
}
group.notify(queue: .main) {
// done
}
It is better to use notify than wait, as wait does block the current thread, so it is safe on non-main threads.
You can also use it to perform async tasks:
let group = DispatchGroup()
group.enter()
asyncTask {
group.leave()
}
group.notify(queue: .main) {
// done
}
Or you can even perform any number of parallel tasks of any synchronicity:
let group = DispatchGroup()
group.enter()
asyncTask1 {
group.leave()
}
group.enter() //other way of doing a task with synchronous API
DispatchQueue.global().async {
syncTask1()
group.leave()
}
group.enter()
asyncTask2 {
group.leave()
}
DispatchQueue.global().async(group: group) {
syncTask2()
}
group.notify(queue: .main) {
// runs when all tasks are done
}
It is important to note a few things.
Always check if your asynchronous functions call the completion callback, sometimes third party libraries forget about that, or cases when your self is weak and nobody bothered to check if the body got evaluated when self is nil. If you don't check it then you can potentially hang and never get the notify callback.
Remember to perform all the needed group.enter() and group.async(group: group) calls before you call the group.notify. Otherwise you can get a race condition, and the group.notify block can fire, before you actually finish your tasks.
BAD EXAMPLE
let group = DispatchGroup()
DispatchQueue.global().async {
group.enter()
syncTask1()
group.leave()
}
group.notify(queue: .main) {
// Can run before syncTask1 completes - DON'T DO THIS
}
The answer to the question in your questions body:
I am trying to ensure that each call to the underlying library finishes before the next begins
A serial queue does guarantee that the tasks are progressed in the order you add them to the queue.
I do not really understand the question in the title though:
How does a serial queue ... know when a task is complete?

Resources