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?
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With