Wait for the fast loop cycle with asynchronous network requests to complete

I would like the in loop to send a bunch of network requests to firebase, and then pass the data to the new view controller after the method finishes. Here is my code:

var datesArray = [String: AnyObject]() for key in locationsArray { let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)") ref.observeSingleEventOfType(.Value, withBlock: { snapshot in datesArray["\(key.0)"] = snapshot.value }) } // Segue to new view controller here and pass datesArray once it is complete 

I have a couple of problems. First, how do I wait for the for loop to complete and all network requests to complete? I cannot change the Watch for SingleEventOfType function, it is part of the Firebase SDK. Also, will I create some kind of race condition trying to access dateArray from different iterations of the for loop (hope this makes sense)? I read about GCD and NSOperation, but I got a little confused as this is the first application I created.

Note: Locations array is an array containing the keys that I need to access firebase. It is also important that network requests start asynchronously. I just want to wait until ALL asynchronous requests complete before I pass the dateArray to the next view controller.

+133
asynchronous swift grand-central-dispatch nsoperation
Mar 10 '16 at 2:37
source share
8 answers

You can use distribution groups to trigger an asynchronous callback when all your requests complete.

Here is an example in Swift 4.1 (also works in Swift 3) using dispatch groups to asynchronously execute a callback when all network requests are complete.

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

Exit

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



For those using the older Swift 2.3, here is an example using its syntax:

 override func viewDidLoad() { super.viewDidLoad() let myGroup = dispatch_group_create() for i in 0 ..< 5 { dispatch_group_enter(myGroup) Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in print("Finished request \(i)") dispatch_group_leave(self.myGroup) } } dispatch_group_notify(myGroup, dispatch_get_main_queue(), { print("Finished all requests.") }) } 
+281
Mar 10 '16 at 2:53 on
source share

Xcode 8.3.1 - Swift 3

This is the accepted answer of the pauls, converted to Swift 3:

 let myGroup = DispatchGroup() override func viewDidLoad() { super.viewDidLoad() for i in 0 ..< 5 { myGroup.enter() Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in print("Finished request \(i)") myGroup.leave() } } myGroup.notify(queue: DispatchQueue.main, execute: { print("Finished all requests.") }) } 
+40
Jan 18 '17 at 17:58
source share

Swift 3 or 4

If you are not interested in orders , use @paulvs answer , it works great.

Otherwise, if someone wants to get the result in order, and not run them at the same time, here is the code.

 let dispatchGroup = DispatchGroup() let dispatchQueue = DispatchQueue(label: "any-label-name") let dispatchSemaphore = DispatchSemaphore(value: 0) dispatchQueue.async { // use array categories as an example. for c in self.categories { if let id = c.categoryId { dispatchGroup.enter() self.downloadProductsByCategory(categoryId: id) { success, data in if success, let products = data { self.products.append(products) } dispatchSemaphore.signal() dispatchGroup.leave() } dispatchSemaphore.wait() } } } dispatchGroup.notify(queue: dispatchQueue) { DispatchQueue.main.async { self.refreshOrderTable { _ in self.productCollectionView.reloadData() } } } 
+26
Oct 20 '17 at 15:17
source share

the details

  • Xcode 10.2.1 (10E1001), Swift 5

Decision

 import Foundation class SimultaneousOperationsQueue { typealias CompleteClosure = ()->() private let dispatchQueue: DispatchQueue private lazy var tasksCompletionQueue = DispatchQueue.main private let semaphore: DispatchSemaphore var whenCompleteAll: (()->())? private lazy var numberOfPendingActionsSemaphore = DispatchSemaphore(value: 1) private lazy var _numberOfPendingActions = 0 var numberOfPendingTasks: Int { get { numberOfPendingActionsSemaphore.wait() defer { numberOfPendingActionsSemaphore.signal() } return _numberOfPendingActions } set(value) { numberOfPendingActionsSemaphore.wait() defer { numberOfPendingActionsSemaphore.signal() } _numberOfPendingActions = value } } init(numberOfSimultaneousActions: Int, dispatchQueueLabel: String) { dispatchQueue = DispatchQueue(label: dispatchQueueLabel) semaphore = DispatchSemaphore(value: numberOfSimultaneousActions) } func run(closure: ((@escaping CompleteClosure) -> Void)?) { numberOfPendingTasks += 1 dispatchQueue.async { [weak self] in guard let self = self, let closure = closure else { return } self.semaphore.wait() closure { defer { self.semaphore.signal() } self.numberOfPendingTasks -= 1 if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll { self.tasksCompletionQueue.async { closure() } } } } } func run(closure: (() -> Void)?) { numberOfPendingTasks += 1 dispatchQueue.async { [weak self] in guard let self = self, let closure = closure else { return } self.semaphore.wait(); defer { self.semaphore.signal() } closure() self.numberOfPendingTasks -= 1 if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll { self.tasksCompletionQueue.async { closure() } } } } } 

using

 let queue = SimultaneousOperationsQueue(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString") queue.whenCompleteAll = { print("All Done") } // add task with sync/async code queue.run { completeClosure in // your code here... // Make signal that this closure finished completeClosure() } // add task only with sync code queue.run { // your code here... } 

Full sample

 import UIKit class ViewController: UIViewController { private lazy var queue = { SimultaneousOperationsQueue(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString") }() private weak var button: UIButton! private weak var label: UILabel! override func viewDidLoad() { super.viewDidLoad() let button = UIButton(frame: CGRect(x: 50, y: 80, width: 100, height: 100)) button.setTitleColor(.blue, for: .normal) button.titleLabel?.numberOfLines = 0 view.addSubview(button) self.button = button let label = UILabel(frame: CGRect(x: 180, y: 50, width: 100, height: 100)) label.text = "" label.numberOfLines = 0 label.textAlignment = .natural view.addSubview(label) self.label = label queue.whenCompleteAll = { [weak self] in self?.label.text = "All tasks completed" } //sample1() sample2() } func sample1() { button.setTitle("Run 2 task", for: .normal) button.addTarget(self, action: #selector(sample1Action), for: .touchUpInside) } func sample2() { button.setTitle("Run 10 tasks", for: .normal) button.addTarget(self, action: #selector(sample2Action), for: .touchUpInside) } private func add2Tasks() { queue.run { completeTask in DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds(1)) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)" } completeTask() } } queue.run { sleep(1) DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)" } } } @objc func sample1Action() { label.text = "pending tasks \(queue.numberOfPendingTasks)" add2Tasks() } @objc func sample2Action() { label.text = "pending tasks \(queue.numberOfPendingTasks)" for _ in 0..<5 { add2Tasks() } } } 
+15
Nov 16 '17 at 17:09 on
source share

For this purpose you will need semaphores.

  //Create the semaphore with count equal to the number of requests that will be made. let semaphore = dispatch_semaphore_create(locationsArray.count) for key in locationsArray { let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)") ref.observeSingleEventOfType(.Value, withBlock: { snapshot in datesArray["\(key.0)"] = snapshot.value //For each request completed, signal the semaphore dispatch_semaphore_signal(semaphore) }) } //Wait on the semaphore until all requests are completed let timeoutLengthInNanoSeconds: Int64 = 10000000000 //Adjust the timeout to suit your case let timeout = dispatch_time(DISPATCH_TIME_NOW, timeoutLengthInNanoSeconds) dispatch_semaphore_wait(semaphore, timeout) //When you reach here all request would have been completed or timeout would have occurred. 
+5
Mar 10 '16 at 4:05
source share

Swift 3: You can also use semaphores along the way. This is very useful, in addition, you can accurately track when and which processes will be completed. This was extracted from my code:

  //You have to create your own queue or if you need the Default queue let persons = persistentContainer.viewContext.persons print("How many persons on database: \(persons.count())") let numberOfPersons = persons.count() for eachPerson in persons{ queuePersonDetail.async { self.getPersonDetailAndSave(personId: eachPerson.personId){person2, error in print("Person detail: \(person2?.fullName)") //When we get the completionHandler we send the signal semaphorePersonDetailAndSave.signal() } } } //Here we will wait for i in 0..<numberOfPersons{ semaphorePersonDetailAndSave.wait() NSLog("\(i + 1)/\(persons.count()) completed") } //And here the flow continues... 
+3
Dec 01 '16 at 22:34
source share

Using swift 5:

Try running this code on the playground. You can play with ordered and disordered results.

 let dispatchGroup:DispatchGroup = DispatchGroup() let semaphore = DispatchSemaphore(value: 1) let globalQueue = DispatchQueue(label: "globalQueue") let innerQueue = DispatchQueue(label: "innerQueue") func doLongThing(i:Int, completion:@escaping ()->Void){ print("In \(i)") innerQueue.sync { sleep(2) print("Out \(i)") completion() } } globalQueue.async { for i in 0..<4{ dispatchGroup.enter() doLongThing(i: i) { print("Finished \(i)") semaphore.signal() } semaphore.wait() dispatchGroup.leave() } dispatchGroup.notify(queue: .main) { print("ALL Done") } } print("In the meantime ...") 
0
Jul 12 '19 at 14:26
source share

The sending group is good, but the order of the requests sent is random.

 Finished request 1 Finished request 0 Finished request 2 

In my case, the project, every request that needs to be run is the correct order. If this can help someone:

 public class RequestItem: NSObject { public var urlToCall: String = "" public var method: HTTPMethod = .get public var params: [String: String] = [:] public var headers: [String: String] = [:] } public func trySendRequestsNotSent (trySendRequestsNotSentCompletionHandler: @escaping ([Error]) -> () = { _ in }) { // If there is requests if !requestItemsToSend.isEmpty { let requestItemsToSendCopy = requestItemsToSend NSLog("Send list started") launchRequestsInOrder(requestItemsToSendCopy, 0, [], launchRequestsInOrderCompletionBlock: { index, errors in trySendRequestsNotSentCompletionHandler(errors) }) } else { trySendRequestsNotSentCompletionHandler([]) } } private func launchRequestsInOrder (_ requestItemsToSend: [RequestItem], _ index: Int, _ errors: [Error], launchRequestsInOrderCompletionBlock: @escaping (_ index: Int, _ errors: [Error] ) -> Void) { executeRequest(requestItemsToSend, index, errors, executeRequestCompletionBlock: { currentIndex, errors in if currentIndex < requestItemsToSend.count { // We didn't reach last request, launch next request self.launchRequestsInOrder(requestItemsToSend, currentIndex, errors, launchRequestsInOrderCompletionBlock: { index, errors in launchRequestsInOrderCompletionBlock(currentIndex, errors) }) } else { // We parse and send all requests NSLog("Send list finished") launchRequestsInOrderCompletionBlock(currentIndex, errors) } }) } private func executeRequest (_ requestItemsToSend: [RequestItem], _ index: Int, _ errors: [Error], executeRequestCompletionBlock: @escaping (_ index: Int, _ errors: [Error]) -> Void) { NSLog("Send request %d", index) Alamofire.request(requestItemsToSend[index].urlToCall, method: requestItemsToSend[index].method, parameters: requestItemsToSend[index].params, headers: requestItemsToSend[index].headers).responseJSON { response in var errors: [Error] = errors switch response.result { case .success: // Request sended successfully, we can remove it from not sended request array self.requestItemsToSend.remove(at: index) break case .failure: // Still not send we append arror errors.append(response.result.error!) break } NSLog("Receive request %d", index) executeRequestCompletionBlock(index+1, errors) } } 

Call:

 trySendRequestsNotSent() 

Result:

 Send list started Send request 0 Receive request 0 Send request 1 Receive request 1 Send request 2 Receive request 2 ... Send list finished 

See Gist for more details.

-one
Sep 01 '17 at 15:43 on
source share



All Articles