How to Make a Function with a Loop Asynchronous in Swift

Wait until swift for loop with asynchronous network requests finishes executing

You can use dispatch groups to fire an asynchronous callback when all your requests finish.

Here's an example using dispatch groups to execute a callback asynchronously when multiple networking requests have all finished.

override func viewDidLoad() {
super.viewDidLoad()

let myGroup = DispatchGroup()

for i in 0 ..< 5 {
myGroup.enter()

Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
myGroup.leave()
}
}

myGroup.notify(queue: .main) {
print("Finished all requests.")
}
}

Output

Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.

Swift async/await in for loop

The await id means geting an id element from the staticImageIDsURL.lines is an asynchronous operation in itself.

for await id in staticImageIDsURL.lines

This operation has to complete before we enter for loop's body for that iteration. You should read AsyncSequence docs to know more on this OR can watch Meet AsyncSequence WWDC 2021 session.


For each iteration, you are waiting for current operation to complete when you say this.

let thumbnail = await fetchThumbnail(for: id)

This line will suspend the function each time a new fetch call is initiated, so these thumbnails calls are guaranteed to be completed sequentially. These calls NEVER happen in parallel, first has to complete before second one is initiated.

Swift Make For Loop wait for Asynchronous function to end

Can't help you much because your code is such a pain to read. Use ObjectMapper to reduce the level (and the effort) to map these fields. Anyhow, you can use dispatch_group to synchronize the various async calls:

let group = dispatch_group_create()
for i in 1..<bids+1 {
// ...
dispatch_group_enter(group)
LOGOS_REF.child("\(phoneNumber)").dataWithMaxSize(3 * 1024 * 1024) {
// ...

dispatch_group_leave(group)
}
}

// Wait for all async calls to complete before proceeding
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

Each dispatch_group_enter() should be balanced with a dispatch_group_leave() when the async task is complete.

Swift: For Loop Wait Until Response And Return Value According To Response

It's possible to make this synchronous using a DispatchGroup, but this must never be called on the main queue.

// Blocking function. Must not be called on main queue!
func returnFirstKidIsGamer(kidsIds: [String]) -> String? {
let group = DispatchGroup()
var result: String? = nil

for kidID in kidsIds {
// Wait for previous request to finish before trying again.
group.enter()
DataService.instance.isKidAGamer(kidID: kidID) { (isGamer) in
if isGamer {
result = kidId
}
group.leave()
}
group.wait()
guard result == nil else { break }
}
return result
}

This calls group.enter() before entering each loop, and group.leave() when each step completes. It then waits for the step to complete before moving on.

This function is synchronous. It blocks the queue and so must never be called on the main queue. You have to move it to the background with something like this:

DispatchQueue.global(qos: .userInitiated).async {
let kidId = returnFirstKidIsGamer(kidsIds: [kids])
DispatchQueue.main.async {
doSomethingInTheUIWithValue(kidId)
}
}

Note that this returns String?, not String, since no id may be found.

As a rule, you shouldn't do this. You should use a query. But this is how you make asynchronous functions into synchronous functions when needed.

Swift: Asynchronous call in a for loop

How about using this structure?

let workGroup = dispatch_group_create()

for i in 0..<addressArray.count {

dispatch_group_enter(workGroup)

performGeoCoding({ successCallback :

dispatch_group_leave(workGroup)

})
}

dispatch_group_notify(workGroup, dispatch_get_main_queue()){
successCallback()
printAddressList()
}

There is very nice tutorial about dispatch_group here.

Execute for loop with optional asynchronous calls in order

You need to use DispatchSemaphore to execute them in order

 //MARK: getWishes
static func getWishes(dataSourceArray: [Wishlist], completion: @escaping (_ success: Bool, _ dataArray: [Wishlist]) -> Void){

var dataSourceArrayWithWishes = dataSourceArray

let db = Firestore.firestore()
let userID = Auth.auth().currentUser!.uid
let group = DispatchGroup()
let dispatchSemaphore = DispatchSemaphore(value: 0)
for list in dataSourceArray {
group.enter()
db.collection("users").document(userID).collection("wishlists").document(list.name).collection("wünsche").order(by: "wishCounter").getDocuments() { ( querySnapshot, error) in

defer {

print("leaving scope:\(String(describing: querySnapshot?.count))")
group.leave()


}

if let error = error {
print(error.localizedDescription)
completion(false, dataSourceArrayWithWishes)
} else {
// dispatch group to make sure completion only fires when for loop is finished

// append every Wish to array at wishIDX
let dispatchQueue = DispatchQueue(label: "taskQueue")
dispatchQueue.async {
for document in querySnapshot!.documents {
group.enter()

let documentData = document.data()
let name = documentData["name"] as? String ?? ""
let link = documentData["link"] as? String ?? ""
let price = documentData["price"] as? String ?? ""
let note = documentData["note"] as? String ?? ""
let imageUrlString = document["imageUrl"] as? String ?? ""
let wishIDX = documentData["wishlistIDX"] as? Int ?? 0


if let imageUrl = URL(string: imageUrlString) {


KingfisherManager.shared.retrieveImage(with: imageUrl, options: nil, progressBlock: nil, completionHandler: { result in

var image = UIImage()

switch result {
case .success(let abc):
image = abc.image

case .failure(let error):
print(error)
break
}

dataSourceArrayWithWishes[wishIDX].wishes.append(Wish(name: name, link: link, price: price, note: note, image: image, checkedStatus: false))


print("Signal for next one")

dispatchSemaphore.signal()
group.leave()

})
print("wait for next one")
dispatchSemaphore.wait()
} else {
dataSourceArrayWithWishes[wishIDX].wishes.append(Wish(name: name, link: link, price: price, note: note, image: nil, checkedStatus: false))
}
}
}
// for loop is finished -> fire completion

}
}
}

group.notify(queue: DispatchQueue.main) {
print("notify")
completion(true, dataSourceArrayWithWishes)
}
}

Swift: Async method into while loop

You can do this with a recursive function. I haven't tested this code but I think it could look a bit like this

func asyncRepeater(userId:Int, foundIdCompletion: (userId:Int)->()){
checkId(userId, completionHandler: { (success:Bool) -> () in
if success {
foundIdCompletion(userId:userId)
} else {
asyncRepeater(userId:Int.random(1...1000), completionHandler: completionHandler)
}
})
}

Swift Async let with loop

You can use a task group. See Tasks and Task Groups section of the The Swift Programming Language: Concurrency (which would appear to be where you got your example).

One can use withTaskGroup(of:returning:body:) to create a task group to run tasks in parallel, but then collate all the results together at the end.

E.g. here is an example that creates child tasks that return a tuple of “name” and ”image”, and the group returns a combined dictionary of those name strings with their associated image values:

func downloadImages(names: [String]) async -> [String: UIImage] {
await withTaskGroup(
of: (String, UIImage).self,
returning: [String: UIImage].self
) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}

var images: [String: UIImage] = [:]

for await result in group {
images[result.0] = result.1
}

return images
}
}

Or, more concisely:

func downloadImages(names: [String]) async -> [String: UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}

return await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
}
}

They run in parallel:

Sample Image

But you can extract them from the dictionary of results:

let stooges = ["moe", "larry", "curly"]
let images = await downloadImages(names: stooges)

imageView1.image = images["moe"]
imageView2.image = images["larry"]
imageView3.image = images["curly"]

Or if you want an array sorted in the original order, just build an array from the dictionary:

func downloadImages(names: [String]) async -> [UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}

let dictionary = await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
return names.compactMap { dictionary[$0] }
}
}

How to use Dispatch Group in a loop with asynchronous Function Swift?

Leave within the async block not outside of it ... and enter should be equal to leave so put it in defer statement to always execute before leaving the scope

  func Promise_searchedDataFromDB(stringArray:[String]) {
for id in stringArray {
myGroup.enter()
collectionRef.getDocuments { (querySnapshot, error) in
defer{ myGroup.leave() }

if error != nil {
return
}
else {
guard let snapshot = querySnapshot else {return}
for document in snapshot.documents {

let myData = document.data()
if StaticVariable == true {

self.typeOfListing = myData["Type"] as? String ?? "Not Found"
self.charges = Int(myData["Charges"] as? String ?? "Not Found") ?? 0
self.nameOfListing = myData["Title"] as? String ?? "Not Found"
self.currency = myData["Currency"] as? String ?? "Not Found"
self.days = myData["Days"] as? String ?? "Not Found"
self.details = myData["Description"] as? String ?? "Not Found"
self.cityName = myData["City"] as? String ?? "Ghost"

let dataArray = CellComponents(image: UIImage(named: "b")!, typeOfListing: self.typeOfListing , charges: self.charges, rating: 4.1, nameOfListing: self.nameOfListing , cityName: self.cityName, detail: self.details, currency: self.currency, days: self.days)
self.ArrayToHoldSearchedListing.append(dataArray)
self.tableView.reloadData()
}
}
}
}


}

myGroup.notify(queue: .main) {
print("All done")
}
}


Related Topics



Leave a reply



Submit