简体   繁体   中英

Pausing while downloading Firestore data

I am using Google Firestore to store data for my App's users. When the app loads, if a user is logged in, I want to get their data. I need to access three different documents, so I have created a Loading viewController that calls three functions like this:

override func viewDidLoad() {
    super.viewDidLoad()
    loadingFunction1()
    loadingFucntion2()
    loadingFunction3()
    ...
    self.performSegue(withIdentifier: "goToNextScreen", sender: nil)
}

Each function looks a little like this:

func loadingFunction1() {
    self.database.collection("doc").getDocuments() { (querySnapshot, error) in
       ...get document from Firestore and store it locally
    }
}

I need to load all of the data before the segue takes the app to the next screen.

I have tried:

  • completion handlers for each function - they each execute in the correct order, but the segue fires before they are all done.
  • nesting completion handlers - putting each function in the completion handler for the prior function, still the segue fires before they are done.
  • putting all three function calls in a DispatchGroup, still fires before they are done.
  • instead of doing the DispatchGroup/Queue thing in the ViewController, I have done it inside each function in the class that holds the three functions. Still fires the segue before they are done.

I have followed Ray Wenderlich's tutorial on DispatchGroups ( https://www.raywenderlich.com/148515/grand-central-dispatch-tutorial-swift-3-part-2 )

I have tried this Stack Overflow question ( iOS - swift 3 - DispatchGroup )

I have read DispatchGroup not working in Swift 3 and How do I use DispatchGroup / GCD to execute functions sequentially in swift? and how to use a completion handler to await the completion of a firestore request and I am still stumped.

How do I make my app execute each of the three functions completely before moving on to the next action. I don't even care what order the three functions are carried out, just so long as they are completely done before moving on.

BTW, my ViewController has a very nice animated Activity Indicator View to entertain the user while this is all happening.

UPDATE with Solution:

I adopted the array of Bools suggestion, with the didSet idea from the comments:

var completedRequests: [Bool] = [false, false, false] {

    didSet {
        segueWhenAllRequestsAreComplete()
    }
}

However, that wasn't enough. I had to add both an escaping completion handler and a dispatchGroup to each function, like this:

func loadingFunction1(completion: @escaping (Bool) -> ()) {

    DispatchQueue.global(qos: .userInteractive).async {
        let downloadGroup = DispatchGroup()
        var success:Bool = false

        downloadGroup.enter()
        self.database.collection("doc").getDocuments() { (querySnapshot, error) in
            if error == nil {
                ...get document from Firestore and store it locally
                success = true
            }
            downloadGroup.leave()
        }
        downloadGroup.wait()
        DispatchQueue.main.async {
            completion(success)
        }
    }   
}

and then call the functions like this:

DataManager.shared.loadData { success in
    self.completedRequests[0] = success
}

So now, at long last, the segue does not fire until all three functions are finished. Seems a little round about, but it works.

You can try to nest calls like this

func loadingFunction1() {
      self.database.collection("doc").getDocuments() { (querySnapshot, error) in
      // ...get document from Firestore and store it locally

       self.loadingFunction2()
    }
 }

and so on until 3

func loadingFunction3() {
      self.database.collection("doc").getDocuments() { (querySnapshot, error) in
      // ...get document from Firestore and store it locally

      self.performSegue(withIdentifier: "goToNextScreen", sender: nil)

    }
 }

Nesting the calls will make them sequential, which is not very efficient and will take much longer to complete. A quicker way would be to keep them running concurrently as you have at the moment, and when each one completes, check if it is the last one to complete.

Firstly, add an array of requests that are pending and a way to check they are all completed to your view controller:

var completedRequests: [Bool] = [false, false, false]

func segueWhenAllRequestsCompleted() {
    if !completedRequests.contains(false) {
        performSegue(withIdentifier: "goToNextScreen", sender: nil)
    }
}

Then, in each of your loading functions:

func loadingFunction1() {
    completedRequests[0] = false
    self.database.collection("doc").getDocuments() { (querySnapshot, error) in
       ...get document from Firestore and store it locally

       self.completedRequests[0] = true
       self.segueWhenAllRequestsCompleted()
    }
}

func loadingFunction2() {
    completedRequests[1] = false
    self.database.collection("doc").getDocuments() { (querySnapshot, error) in
       ...get document from Firestore and store it locally

       self.completedRequests[1] = true
       self.segueWhenAllRequestsCompleted()
    }
}

func loadingFunction3() {
    completedRequests[2] = false
    self.database.collection("doc").getDocuments() { (querySnapshot, error) in
       ...get document from Firestore and store it locally

       self.completedRequests[2] = true
       self.segueWhenAllRequestsCompleted()
    }
}

This way, your view controller will segue when all the requests have completed and they will still run at the same time.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM