Swift 4 Async Call with for Loop Execute in Order Using Dispatchgroup, Dispatchqueue and Dispatchsemaphore

Swift 4 async call with for loop execute in order using DispatchGroup, DispatchQueue and DispatchSemaphore

You can put the whole loop in a block instead of putting just the download function in a block:

dispatchQueue.async {
for c in self.categories {
if let id = c.categoryId {
self.downloadProductsByCategory(categoryId: id) { success, data in
if success, let products = data {
self.products.append(products)
}

dispatchSemaphore.signal()
}
dispatchSemaphore.wait()
}
}
}

You can simplify your code by using compactMap to unwrap your product ids:

for id in self.categories.compactMap({$0.categoryId}) { ... }

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)
}
}

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.

How does the semaphore keep async loop in order?

The solution is in your comment get image with asynchronous completion handler. Without the semaphore all image downloads would be started at the same time and race for completion, so the image that downloads fastest would be added to the array first.

So after you start your download you immediately wait on your semaphore. This will block until it is signaled in the callback closure from the getData method. Only then the loop can continue to the next document and download it. This way you download one file after another and block the current thread while the downloads are running.

Using a serial queue is not an option here, since this would only cause the downloads to start serially, but you can’t affect the order in which they finish.

This is a rather inefficient though. Your network layer probably can run faster if you give it multiple requests at the same time (think of parallel downloads and HTTP pipelining). Also you're 'wasting' a thread which could do some different work in the meantime. If there is more work to do at the same time GCD will spawn another thread which wastes memory and other resources.

A better pattern would be to skip the semaphore, let the downloads run in parallel and store the image directly at the correct index in your array. This of course means you have to prepare an array of the appropriate size beforehand, and you have to think of a placeholder for missing or failed images. Optionals would do the trick nicely:

var images: [UIImage?] = Array(repeating: nil, count: snapshot.documents.count)

for (index, doc) in snapshot.documents.enumerated() {

// create data object for array

dispatchGroup.enter()

// get image with asynchronous completion handler
Storage.storage().reference(forURL: imageId).getData(maxSize: 1048576) { data, error in

defer {
dispatchGroup.leave()
}

if let imageData = data,
error == nil {
// add image to data object
images[index] = image
}
}
}

DispatchGroup in For Loop

execute the below code to get the proper output:

for i in 1...3 {
let semaphore = DispatchSemaphore(value: 0) // create a semaphore with a value 0. signal() will make the value 1.
print("INDEX \(i)")
asynchronousOperation(index: i, completion: {
print("HELLO \(i)")
semaphore.signal() // once you make the signal(), then only next loop will get executed.
})
print("OUTSIDE \(i)")
semaphore.wait() // asking the semaphore to wait, till it gets the signal.
}

func asynchronousOperation(index: Int, completion: @escaping () -> ()) {
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+5) {
print("DONE \(index)")
completion()
}
}

Output :

INDEX 1
OUTSIDE 1
DONE 1
HELLO 1

INDEX 2
OUTSIDE 2
DONE 2
HELLO 2

INDEX 3
OUTSIDE 3
DONE 3
HELLO 3

How to create multiple delays using a dispatch queue?

// Code I want to run every 1 second for 20 times

What you're looking for is a Timer.

https://developer.apple.com/documentation/foundation/timer

var timer : Timer?
var times = 0
@objc func fired(_ t:Timer) {
times += 1
print("do your code") // do your code here
if times == 20 {
t.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self,
selector: #selector(fired), userInfo: nil,
repeats: true)
}

You will see that print("do your code") runs every 1 second 20 times.

When to use Semaphore instead of Dispatch Group?

Conceptually, both of DispatchGroup and Semaphore serve the same purpose (unless I misunderstand something).

The above is not exactly true. You can use a semaphore to do the same thing as a dispatch group but it is much more general.

Dispatch groups are used when you have a load of things you want to do that can all happen at once, but you need to wait for them all to finish before doing something else.

Semaphores can be used for the above but they are general purpose synchronisation objects and can be used for many other purposes too. The concept of a semaphore is not limited to Apple and can be found in many operating systems.

In general, a semaphore has a value which is a non negative integer and two operations:

  • wait If the value is not zero, decrement it, otherwise block until something signals the semaphore.

  • signal If there are threads waiting, unblock one of them, otherwise increment the value.

Needless to say both operations have to be thread safe. In olden days, when you only had one CPU, you'd simply disable interrupts whilst manipulating the value and the queue of waiting threads. Nowadays, it is more complicated because of multiple CPU cores and on chip caches etc.

A semaphore can be used in any case where you have a resource that can be accessed by at most N threads at the same time. You set the semaphore's initial value to N and then the first N threads that wait on it are not blocked but the next thread has to wait until one of the first N threads has signaled the semaphore. The simplest case is N = 1. In that case, the semaphore behaves like a mutex lock.

A semaphore can be used to emulate a dispatch group. You start the sempahore at 0, start all the tasks - tracking how many you have started and wait on the semaphore that number of times. Each task must signal the semaphore when it completes.

However, there are some gotchas. For example, you need a separate count to know how many times to wait. If you want to be able to add more tasks to the group after you have started waiting, the count can only be updated in a mutex protected block and that may lead to problems with deadlocking. Also, I think the Dispatch implementation of semaphores might be vulnerable to priority inversion. Priority inversion occurs when a high priority thread waits for a resource that a low priority has grabbed. The high priority thread is blocked until the low priority thread releases the resource. If there is a medium priority thread running, this may never happen.

You can pretty much do anything with a semaphore that other higher level synchronisation abstractions can do, but doing it right is often a tricky business to get right. The higher level abstractions are (hopefully) carefully written and you should use them in preference to a "roll your own" implementation with semaphores, if possible.



Related Topics



Leave a reply



Submit