How do I wait for a task to finish before proceeding with the program flow?

Hi, So I am using this function getListAtFIRStore() which is fetching data from firestore and returning it. But the fetching takes some time and before that happens the function already returns back the array which is empty at that moment.

How do I wait for the fetching task to be completed before the function returns the array back?

class CheckForDownloads {

    

    var userName = String()

    

    func refreshResources(forUser username: String) {

        self.userName = username

        let listOfImagesAtFirestore =  getListAtFIRStore()

        print(listOfImagesAtFirestore)

    }

    

    

    func getListAtFIRStore() -> [String]{

        let root = Storage.storage().reference()

        var str3 = [String]()

        

        root.child("MagicFrame/\(userName)/images").listAll { (result, error) in

            if let error = error {

                print("Error in fetching list from firestore \(error.localizedDescription)")

            }else{

                for item in result.items{

                    if let str1 = (item.description.components(separatedBy: ["/"]).last) {

                        if let str2 = (str1.components(separatedBy: ["."])).first{

                            print(str2)

                            str3.append(str2)

                        }

                        

                    }

                }

            }

        }

        return str3

    }

    

}
Answered by blueturtle in 715039022

You can use completion handler for this specific use case. Take a look at this: https://firebase.blog/posts/2018/07/swift-closures-and-firebase-handling Maybe it will help you

Accepted Answer

You can use completion handler for this specific use case. Take a look at this: https://firebase.blog/posts/2018/07/swift-closures-and-firebase-handling Maybe it will help you

Hi,

Completion handler did work for me in a different case, but here I am facing the same issue again and completion handler is not giving the expected result. Since I am new to Swift I am not sure if I have implemented it correctly.

Here I am trying to download images based on a list of names from Firestore, and then adding them to a resource group. I am expecting when the statement trackedImages = trackedImages.union(self.newReferenceImages) executes, newReferenceImages should have the downloaded images.

But it gets executed before the downloading is completed.

Please help.

func downloadArResources(listOfImages imageList: [String] ){

        if !imageList.isEmpty {

            for imageName in imageList {

                print("Image working on: \(imageName)")

                downloadAndAddToSet(name: imageName){

                    if var trackedImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: Bundle.main){

                        trackedImages = trackedImages.union(self.newReferenceImages)

                        print("new Bundle images = \(trackedImages.description)")

                        

                    }

                }

            }

        }

    }

    

    

    func downloadAndAddToSet(name imageName: String, completion: @escaping () -> Void){

        let FIRImageName = self.root.child("MagicFrame/\(self.userName)/images/\(imageName).jpg")

        FIRImageName.getData(maxSize: 5 * 1024 * 1024) { data, error in

            if let error = error {

                print("Error in downloading image: \(error)")

            } else {

                if let image = UIImage(data: data!){

                    if let cgimage = image.cgImage {

                        let newImage = ARReferenceImage(cgimage, orientation: .up, physicalWidth: 0.2)

                        newImage.name = imageName

                        self.newReferenceImages.insert(newImage)

                    }

                }

            }

        }

        completion()

    }

Here is a simple snippet on how you should use completionHandlers

func loadImageAsync(completion: @escaping (UIImage?, Error?) -> Void) {
        guard let url = URL(string: "https://rickandmortyapi.com/api/character/avatar/2.jpeg") else {
            return
        }

        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                DispatchQueue.main.async {
                    completion(nil, error)
                }
                return
            }
            let image = UIImage(data: data)
            DispatchQueue.main.async {
                completion(image, nil)
            }
        }.resume()
    }

See whats happening here? After the download occurs, we tell the main thread to dispatch our result. That's when we call the completion handler. That's like "Hey, i'm done, do whatever you want with these results."

Also important: UI updates should ALWAYS happen on the main thread.

Of course the syntax is a little different from what you're using but the principle is the same.

And the usage should be something like:

loadImageAsync { [weak self] img, err in

            if let image = img {
                self?.imageContainer.image = image
            } else {
                print("error \(String(describing: err))")
            }

        }

You should always use weak self or unowned self when possible, it prevent weird crashes and and memory leaks.

Also, try to nest the if let when possible, it reduces the cognitives complexity and makes your code more readable. So, instead of this:

        if let image = UIImage(data: data!){
                if let cgimage = image.cgImage {
                 //...
               }
        }

Try something like:

if let data = data, let image = UImage(data: data), let cgImage = image.cgImage {
//...
}

And you should never, ever force unwrap a property unless you are 100% sure it has something in it. Try unwraping it using guard let or if let for your own security.

I like blueturtle’s response in general, but I’m going to disagree with this:

You should always use weak self or unowned self when possible, it prevent weird crashes and and memory leaks.

The ‘weak self dance’ is rarely required when dealing with completion handlers. For example, in the URLSession case, the session guarantees to call the completion handler eventually — the request will either run to completion, or fail, or time out — and when that happens you break any retain cycles you formed.

The weak self dance is important in situations where you install a callback that isn’t guaranteed to be called, for example, when using the addObserver(forName:object:queue:using:) method on NotificationCenter.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks @blueturtle I have a much better understanding of how the completionHandler works. I have tried implementing it accordingly and it did work, but I am still facing the same problem when I use it in calls between classes.

func refreshResources(forUser username: String, ARresourceReference imagesPresent: Set<ARReferenceImage>, completion: @escaping (Set<ARReferenceImage>) -> Void) {

        self.userName = username
        self.newReferenceImages = imagesPresent
        getListandDownload()
        DispatchQueue.main.async() {
              completion(self.newReferenceImages)
           }
}

Here getListandDownload() is a function of class CheckForDownloads that will perform some logic, download images and add them to newReferenceImages. Although all this takes some time (depends on network speed) it is working fine with the use of completionHandler.

getListandDownload() is being called from a different class ARViewController's viewWillAppear method

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    let configuration = ARImageTrackingConfiguration()       
    guard let trackedImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: Bundle.main) else{
                print("Problem in initializing AR Resources bundle")
                return
            }

    self.checkForDownloads.refreshResources(forUser: self.username, ARresourceReference: trackedImages) { imageSet in
                 configuration.trackingImages = imageSet
                 configuration.maximumNumberOfTrackedImages = 1
                 // Run the view's session
                 self.sceneView.session.run(configuration)
            }
}

Here I am expecting that only after getListandDownload() is completed, the completion(self.newReferenceImages) should be called that will set the configurations and run the session. But what is happening now is that the completionHandler runs way sooner and debug log shows that the downloading is happening afterwards.

I also tried using

DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
    completion(self.newReferenceImages)
 }

But it fails if the downloading takes more than 5 seconds, not what I am looking for.

How do I wait for a task to finish before proceeding with the program flow?
 
 
Q