1

To load some information in my app's view, I need it to finish networking because some methods depend on the result. I looked into serial DispatchQueue and .async methods, but it's not working as expected.

Here is what I tried so far. I defined 3 blocks:

  1. Where I'd get hold of the user's email, if any
  2. The email would be used as input for a method called getData, which reads the database based on user's email address
  3. This block would populate the table view with the data from the database. I've laid it out like this, but I'm getting an error which tells me the second block still executes before we have access to user's email address, i.e. the first block is finished. Any help is appreciated, thanks in advance!
        let serialQueue = DispatchQueue(label: "com.queue.serial")
        let block1 = DispatchWorkItem {
            GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
                if error != nil || user == nil {
                    print("unable to identify user")
                } else {
                    print(user!.profile?.email ?? "")
                    self.email = user!.profile?.email ?? ""
                    print("email is: \(self.email)")
                }
            }
        }
        
        let block2 = DispatchWorkItem{
            self.getData(self.email)
        }
        
        let block3 = DispatchWorkItem {
            DispatchQueue.main.async {
                self.todoListTable.reloadData()
            }
        }
        serialQueue.async(execute: block1)
        block1.notify(queue: serialQueue, execute: block2)
        block2.notify(queue: serialQueue, execute: block3)
SwissMark
  • 1,041
  • 12
  • 21
Shakiba
  • 11
  • 2

1 Answers1

0

Your problem is that you are dispatching asynchronous work inside your work items; GIDSignIn.sharedInstance.restorePreviousSignIn "returns" immediately but fires the completion handler later when the work is actually done. Since it has returned, the dispatch queue considers the work item complete and so moves on to the next item.

The traditional approach is to invoke the second operation from the completion handler of the first (and the third from the second).

Assuming you modified self.getData() to have a completion handler, it would look something like this:

GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
    if error != nil || user == nil {
        print("unable to identify user")
    } else {
       print(user!.profile?.email ?? "")
       self.email = user!.profile?.email ?? ""
       print("email is: \(self.email)")
       self.getData(self.email) {
           DispatchQueue.main.async {
               self.todoListTable.reloadData()
           }
       }
    }
}

You can see you quickly end up with a "pyramid of doom".

The modern way is to use async/await. This requires your functions to support this approach and imposes a minimum iOS level of iOS 13.

It would look something like

do {
    let user = try await GIDSignIn.sharedInstance.restorePreviousSignIn()
    if let email = user.profile?.email {
       let fetchedData = try await getData(email)
       self.data = fetchedData
            self.tableView.reloadData()
    }
} catch {
    print("There was an error \(error)")
}

Ahh. Much simpler to read.

Paulw11
  • 108,386
  • 14
  • 159
  • 186