Search code examples
iosxcodemultithreadinggoogle-api-objc-client

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


Solution

  • 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
    }