简体   繁体   中英

Dispatch.notify being called before task has completed?

Im trying to download data from Firestore, append it to an array and then append it to another array once all the data has been downloaded. I tried using a completion handler but it returns even if the data hasn't all been downloaded. So I tried using a DispatchGroup however dispatch.notify is being called before my task has finished.

here is my code. I am calling dispatch.begin inside of the last else bracket otherwise leave and enter aren't balanced for some reason?

How would I wait until all the data has downloaded and then call the completion inside the dispatch.notify?

func SameUniRelatedCourses(completion: @escaping (_ success: Bool) -> Void) {
        DispatchQueue.main.async {
            self.spinner.startAnimating()
        }
        service.loadUniversityAndCourse { (uni, course) in
        let related = RelatedCourses()
        let relatedCourseArray = related.getRelatedCourses(userCourse: course)//.prefix(4)
        for Course in relatedCourseArray {
            let UniRef = Firestore.firestore().collection("User-Courses").document(Course)
                UniRef.getDocument { (snapshot, error) in
            if let error = error{
                print(error.localizedDescription)
            }
            else {
                //append their data to an array
                guard let data = snapshot?.data() else {return}
                let stringArray = Array(data.keys)
                for user in stringArray {
                    let usersRef = Firestore.firestore().collection("users").document(user)
                    usersRef.getDocument { (snapshot, error) in
                if let error = error {
                    print(error.localizedDescription)
                }
                else {
                    let data = snapshot?.data()
                    //print("raw data", data?["username"]!)
                    if let dictionary = data as [String:AnyObject]? {
                    let Info = UserInfo(dictionary: dictionary)
                        if Info.University != uni {
                            //print("Not in their uni",Info.username!)
                        }
                        else if self.sameUniSameCourse.contains(where: { $0.uid == Info.uid }) {
                           //print("already in sameunisamecourse",Info.username!)
                        }
                        else {
                            print("entering")
                            self.dispatchGroup.enter()
                            print("1", Info.username!)
                           self.sameUniRelatedCourses.append(Info)
                            self.sameUniRelatedCourses.sort { (time1, time2) -> Bool in
                                return time2.Created!.seconds/1000 > time1.Created!.seconds/1000
                            }
                            print("leaving")
                            self.dispatchGroup.leave()
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
        self.dispatchGroup.notify(queue: .main) {
            print("done")
            }
    }

the second function

func sameUniversity(completion: @escaping (_ success: Bool) -> Void) {
        DispatchQueue.main.async {
            self.spinner.startAnimating()
        }
        
        self.dispatchGroup.enter()
        service.loadUniversityAndCourse { (uni, course) in
            defer{ self.dispatchGroup.leave() }
        let UniRef = Firestore.firestore().collection("User-Universities").document(uni)
        UniRef.getDocument { (snapshot, error) in
        if let error = error{
            print(error.localizedDescription)
        }
        else {
            //append their data to an array
            guard let data = snapshot?.data() else {return}
            let stringArray = Array(data.keys)
            for user in stringArray {
                let usersRef = Firestore.firestore().collection("users").document(user)
            usersRef.getDocument { (snapshot, error) in
            if let error = error {
                print(error.localizedDescription)
            }
            else {
                let data = snapshot?.data()
                //print("raw data", data?["username"])
                if let dictionary = data as [String:AnyObject]? {
                let Info = UserInfo(dictionary: dictionary)
                    if self.sameUniSameCourse.contains(where: { $0.uid == Info.uid }) || Info.uid == Auth.auth().currentUser?.uid || self.sameUniRelatedCourses.contains(where: { $0.uid == Info.uid }) {
                        //print("already in master array", Info.username!)
                    }
                    else {
                        print("2", Info.username!)
                        self.sameUni.append(Info)
                        self.sameUni.sort { (time1, time2) -> Bool in
                            return time1.Created!.seconds/1000 > time2.Created!.seconds/1000
                                }
                            }
                        }
                    }
                }
            }
        }
    }
  }

        self.dispatchGroup.notify(queue: .main) {
        print("done")
        //call Completoion here
        self.masterArray.append(contentsOf: self.sameUni)
        self.spinner.stopAnimating()
        self.tableView.reloadData()
        completion(true)
        }

}

You need to use Dispatch Group like this. Enter before performing the asynchronous task and call leave inside the completion closure

 func SameUniRelatedCourses(completion: @escaping (_ success: Bool) -> Void) {
    DispatchQueue.main.async {
        self.spinner.startAnimating()
    }
    
    self.dispatchGroup.enter()
    service.loadUniversityAndCourse { (uni, course) in
       defer{ self.dispatchGroup.leave() }
        
        
    let related = RelatedCourses()
    let relatedCourseArray = related.getRelatedCourses(userCourse: course)//.prefix(4)
    for Course in relatedCourseArray {
        self.dispatchGroup.enter()
        let UniRef = Firestore.firestore().collection("User-Courses").document(Course)
            UniRef.getDocument { (snapshot, error) in
                defer{ self.dispatchGroup.leave() }
        if let error = error{
            print(error.localizedDescription)
        }
        else {
            //append their data to an array
            guard let data = snapshot?.data() else {return}
            let stringArray = Array(data.keys)
            for user in stringArray {
                self.dispatchGroup.enter()
                let usersRef = Firestore.firestore().collection("users").document(user)
                usersRef.getDocument { (snapshot, error) in
                    defer{ self.dispatchGroup.leave() }
            if let error = error {
                print(error.localizedDescription)
            }
            else {
                let data = snapshot?.data()
                //print("raw data", data?["username"]!)
                if let dictionary = data as [String:AnyObject]? {
                let Info = UserInfo(dictionary: dictionary)
                    if Info.University != uni {
                        //print("Not in their uni",Info.username!)
                    }
                    else if self.sameUniSameCourse.contains(where: { $0.uid == Info.uid }) {
                       //print("already in sameunisamecourse",Info.username!)
                    }
                    else {
                        print("entering")
                        
                        print("1", Info.username!)
                       self.sameUniRelatedCourses.append(Info)
                        self.sameUniRelatedCourses.sort { (time1, time2) -> Bool in
                            return time2.Created!.seconds/1000 > time1.Created!.seconds/1000
                        }
                        print("leaving")
                       
                                }
                            }
                        }
                    
                     
                    
                    }
                }
            }
                
        }
    }
        
}
    self.dispatchGroup.notify(queue: .main) {
        print("done")
        //call Completoion here
        }
}

Second function

 func sameUniversity(completion: @escaping (_ success: Bool) -> Void) {
            DispatchQueue.main.async {
                self.spinner.startAnimating()
            }
            
            self.dispatchGroup.enter()
            service.loadUniversityAndCourse { (uni, course) in
                defer{ self.dispatchGroup.leave() }
            let UniRef = Firestore.firestore().collection("User-Universities").document(uni)
                
                self.dispatchGroup.enter()
            UniRef.getDocument { (snapshot, error) in
                defer{ self.dispatchGroup.leave() }
            if let error = error{
                print(error.localizedDescription)
            }
            else {
                //append their data to an array
                guard let data = snapshot?.data() else {return}
                let stringArray = Array(data.keys)
                for user in stringArray {
                    self.dispatchGroup.enter()
                    let usersRef = Firestore.firestore().collection("users").document(user)
                usersRef.getDocument { (snapshot, error) in
                    defer{ self.dispatchGroup.leave() }
                if let error = error {
                    print(error.localizedDescription)
                }
                else {
                    let data = snapshot?.data()
                    //print("raw data", data?["username"])
                    if let dictionary = data as [String:AnyObject]? {
                    let Info = UserInfo(dictionary: dictionary)
                        if self.sameUniSameCourse.contains(where: { $0.uid == Info.uid }) || Info.uid == Auth.auth().currentUser?.uid || self.sameUniRelatedCourses.contains(where: { $0.uid == Info.uid }) {
                            //print("already in master array", Info.username!)
                        }
                        else {
                            print("2", Info.username!)
                            self.sameUni.append(Info)
                            self.sameUni.sort { (time1, time2) -> Bool in
                                return time1.Created!.seconds/1000 > time2.Created!.seconds/1000
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
      }

            self.dispatchGroup.notify(queue: .main) {
            print("done")
            //call Completoion here
            self.masterArray.append(contentsOf: self.sameUni)
            self.spinner.stopAnimating()
            self.tableView.reloadData()
            completion(true)
            }

    }

A DispatchGroup adds the enter() and subtracts leave() calls to determine when the work is done. In your case, you don't enter the group before firing off any async calls you are waiting on. As a result, you are racing past your enter()s and your group finishes immediately.

Here's a small playground to illustrate the idea:

import UIKit
import PlaygroundSupport
var str = "Hello, playground"

// Code will race to the notify before any enter() has been called.

func doAsyncWork_racing(completion: @escaping () -> Void) {
    let group = DispatchGroup()
    
    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
        for i in 0..<5 {
            group.enter()
            print("\(#function) doing task \(i)")
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        print("\(#function) done!!")
        completion()
    }
}

// Code will enter() before calling any async work, so the notify does 
// not get immediately fired off.

func doAsyncWork_correct(completion: @escaping () -> Void) {
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
        for i in 0..<5 {
            group.enter()
            print("\(#function)  doing task \(i)")
            group.leave()
        }
        group.leave()
    }
    
    group.notify(queue: .main) {
        print("\(#function) done!!")
        completion()
    }
}

// Same as before, but work is submitted to a different queue and we
// block the thread with wait(), making the whole function blocking.

func doAsyncWork_waiting(completion: @escaping () -> Void) {
    let group = DispatchGroup()
    let workQueue = DispatchQueue(label: "work")

    // Here's an  example where the group waits until
    //  and block the thread its work is done.
    group.enter()
    workQueue.asyncAfter(deadline: .now() + .seconds(3)) {
        for i in 0..<5 {
            group.enter()
            print("\(#function)  doing task \(i)")
            group.leave()
        }
        group.leave()
    }
    
    group.wait()
    
    print("\(#function) done!!")
    completion()
}

doAsyncWork_racing() {
    doAsyncWork_correct() {
        doAsyncWork_waiting() {
            print("all calls done!")
        }
    }
}

PlaygroundSupport.PlaygroundPage.current.needsIndefiniteExecution = true

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