2

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 *************************************

Christophe
  • 43
  • 7

1 Answers1

1

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
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks for your detailed answer and I realise I should have been more precise. My example actually works when workItem is NOT the Google API function "service.executeQuery(...)", i.e. I receive the notification once the thread has completed. The issue seems really specific to calling this Google function. I tried many syntaxes (also not using dispatch group) and they all worked when I do NOT call this Google function. Lastly, I need to be notified because I need to act upon the notification. Any other idea? Thanks – Christophe Feb 04 '18 at 17:35
  • When you use this `DispatchWorkItem` pattern, at the very least, the `leave` call must be _inside_ the dispatch work item block, not after it. The Google example adds an additional wrinkle because your work item is calling something that is, itself, asynchronous, so the dispatch work item will finish before your asynchronous call is done. So you have to put the `leave` call not only inside the `DispatchWorkItem`, but more accurately within the completion handler of `executeQuery` that is inside that work item. – Rob Feb 04 '18 at 17:44
  • Alright, let me try some examples and I will get back once I have some feedback (positive hopefully!). Thanks – Christophe Feb 04 '18 at 17:53
  • Thank you so much @Rob! It worked! I moved the `group.leave()` inside `displayResultWithTicket2b` and this did the trick. Overall probably not cleanest code I can write but with my limited knowledge of Swift and having to use the Google API that's the best I can do now. You rescued my app after days of trial and error! – Christophe Feb 04 '18 at 20:50