Swift - completion handler in loop
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)
}
}
Swift Completion Handler For Loop to be performed once instead of 10 times due to the loop
Use a DispatchGroup. You can enter the dispatch group each time you call the async code and then leave each time it's done. Then when everything is finished it will call the notify block and you can call your handler. Here's a quick example of what that would look like:
let dispatchGroup = DispatchGroup()
let array = []
for i in array {
dispatchGroup.enter()
somethingAsync() {
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
handler()
}
Swift - How to handle completion block in for-loop?
You can add a completionHandler
to your function which you can call when the animation
is completed:
func makeAnimation(value: Int, onCompletion: @escaping (LOTAnimationView) -> Void) {
let anotherAnimationView = LOTAnimationView(name: gifNames[value])
animationView = anotherAnimationView
animationView?.play(completion: { finished in
print("completed")
onCompletion(animationView)
})
}
And to use it use the following:
makeAnimation(value: 1000000) { (animationView) in
self.view.addSubview(animationView!)
}
Swift- Waiting for asynchronous for-in loop to complete before calling completion handler swift
A couple of thoughts:
Avoid ever calling
wait
from the main thread. The use cases for that are pretty limited. Thenotify
is a much safer way to achieve the same thing.Make sure you call
leave
from every path inside you’re loop. This can be achieved nicely withdefer
block.
So:
func foo(completion: @escaping (Double?) -> Void) {
ref.observeSingleEvent(of: .value) { snapshot in
guard let bills = snapshot.value as? [String: AnyObject] else {
//error
completion(nil)
return
}
let group = DispatchGroup()
var runningTotal = 0.0
for billId in bills.keys {
group.enter()
print("Entering")
Database.database().reference().child("bills").child(billId).observeSingleEvent(of: .value) { snapshot in
defer { group.leave() }
guard let bill = snapshot.value as? [String: AnyObject] else {
return
}
if let amount = bill["amount"] as? Double {
runningTotal += amount
}
print("Leaving")
}
}
group.notify(queue: .main) {
completion(runningTotal)
}
}
}
Swift 4 completion handler for loop not working
You need a DispatchGroup
for multiple asunchounous tasks finish completion
func loopImages(qa: QAClass, assets: [DKAsset], completion: ([PhotoClass]) -> Void) {
var imageCellHolder = [PhotoClass]()
let dispa = DispatchGroup()
for x in assets
{
dispa.enter()
x.fetchOriginalImage(completeBlock: { (image, info) in
let compressedImage = image?.resizeWithWidth(width: 800)
let imageData:Data = compressedImage!.jpegData(compressionQuality: 0.5)!
imageCellHolder.append(PhotoClass(job: String(self.selectedCommunity! + self.selectedLot!), photo: imageData, itemId: qa.itemId))
dispa.leave()
})
}
dispa.notify(queue: .main) {
completion(imageCellHolder)
}
}
Using completion handlers inside while loop
You can try the following:
func getHub(triesLeft: Int = 3, completion: @escaping (Bool, Error?) -> Void) {
let timeoutInSeconds = 1.0
print("starting")
getHubCallAPI(completion: { status, error in
if error == nil {
print(status)
if status != "No documents found" {
print("Hub found")
return completion(true, nil)
}
} else {
print("error")
return completion(false, error) // comment out if the loop should continue on error
}
if triesLeft <= 1 {
print("Tried too many times")
return completion(false, nil)
}
DispatchQueue.main.asyncAfter(deadline: .now() + timeoutInSeconds) {
getHub(triesLeft: triesLeft - 1, completion: completion)
}
})
}
And just call it once like this:
getHub(triesLeft: 2, completion: { ... })
Note that unless you need it for some other reason, there is no need to return (Bool, Error?)
. And the second parameter is always nil
- you may want to propagate your error. You could in theory return (String?, Error?)
.
Related Topics
Heightanchor.Constraint Not Change Height of View
How to Custom The Image of Mkannotation Pin
Swift: Draw a Semi-Sphere in Mkmapview
Transparent Sticky Header UI Collectionview Don't Show Cells Underneath
How to Put a View with Loadmore Button in UItableview After The Cell
Showing Action Sheet in The Custom Cell in Swift
Add Value to Variable Inside Closure in Swift
How to Populate Table Rows, Using a [String] Array Sent from iPhone by Watch Connectivity
How to Get User Nearby My Location in Geofire,Firebase
Programmatically Select All Cells in Tableview So Next Time They Are Pressed They Call Diddeselect
How to Apply Impulse to The Node on Touch Angle
Query Value Between Two Other Values in Firebase
Using a Swiftui List Sidebar in a UIsplitviewcontroller
Multi-Users Chat Room Data Structure in Firebase Database