Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - completion handler in loop [duplicate]

Tags:

ios

swift

I have a method with a completion handler

func postLandGradingImages(cellHolder: Array<ImagesData>, completionHandler:@escaping (_ result:Bool) -> Void) {

        //Define bool for returning data

        var returnedResults = false

        //Call API

        WebService().postLandGradingImages(cellHolder)
        {
            (result: Bool) in

            DispatchQueue.main.async {

                //Return our results

                returnedResults = result
                completionHandler(returnedResults)

            }

        }

    }

and I am calling this method inside a loop like so:

for asset: PHAsset in photoAssets
{
    self.postLandGradingImages(cellHolder: [ImagesData(jobNo: self.JobNo, ImageBytes: imageStr)]) { result in

    }
}

What I am trying to do is if this fails at some point, display an alert and stop looping and after the loop is done and all my calls returned true display an alert at the end.

This is what I have tried:

var returned = false

        for asset: PHAsset in photoAssets
        {
            imageManager.requestImage(for: asset, targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight), contentMode: .aspectFill, options: options, resultHandler: { (image, info) in

                let imageData:Data = UIImagePNGRepresentation(image!)!

                let imageStr = imageData.base64EncodedString()

                self.postLandGradingImages(cellHolder: [ImagesData(jobNo: self.JobNo, ImageBytes: imageStr)]) { result in

                    returned = result

                    if(returned == false)
                    {
                        self.customAlert(title: "Error", message: "There was an error when saving data, please try again later.")
                    }

                }

            })
        }

        if(returned == true)
        {
            self.customAlert(title: "Error", message: “All Good“)
        }

But my alert saying All Good never comes up as returned gets checked before even my first call. What am I doing wrong and how do I accomplish what I am trying to accomplish?

like image 751
user979331 Avatar asked May 08 '18 15:05

user979331


Video Answer


1 Answers

The problem is that your for loop will complete very quickly. In fact, as you've seen, the loop will finish before even one of the completion blocks is called. This is the nature of asynchronous processing.

By using DispatchGroup you can setup your code so it will perform a block of code only after all of the completion blocks have finished, regardless of how quickly the loop itself completes.

Also note that you have two levels of async calls inside your loop.

Below is how you should setup your code. Also note I fixed several other issues such as forced-unwraps.

var returned = true // assume success for all

let group = DispatchGroup()

for asset in photoAssets {
    group.enter() // for imageManager
    imageManager.requestImage(for: asset, targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight), contentMode: .aspectFill, options: options, resultHandler: { (image, info) in
        if let image = image, let let imageData = UIImagePNGRepresentation(image) {
            let imageStr = imageData.base64EncodedString()

            group.enter()
            self.postLandGradingImages(cellHolder: [ImagesData(jobNo: self.JobNo, ImageBytes: imageStr)]) { result in
                if !result {
                    returned = false // we had a failure
                }
                group.leave()
            }
        }
        group.leave() // imageManager
    })
}

group.notify(queue: DispatchQueue.main) {
    if returned {
        self.customAlert(title: "Success", message: “All Good“)
    } else {
        self.customAlert(title: "Error", message: "There was an error when saving data, please try again later.")
    }
}

With all of that in place, you need to update postLandGradingImages. There's no need to use the main queue for the completion handler.

func postLandGradingImages(cellHolder: Array<ImagesData>, completionHandler:@escaping (_ result:Bool) -> Void) {
    //Call API
    WebService().postLandGradingImages(cellHolder) { (result: Bool) in
        //Return our results
        completionHandler(result)
    }
}
like image 185
rmaddy Avatar answered Nov 15 '22 07:11

rmaddy