Wait until swift for loop with asynchronous network requests finishes executing

ID : 20065

viewed : 24

Tags : swiftasynchronousgrand-central-dispatchnsoperationswift

Top 5 Answer for Wait until swift for loop with asynchronous network requests finishes executing

vote vote

91

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. 
vote vote

82

Swift 3 or 4

If you don't care about orders, use @paulvs's answer, it works perfectly.

else just in case if anyone wants to get the result in order instead of fire them concurrently, 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()         }     } } 
vote vote

76

Xcode 8.3.1 - Swift 3

This is the accepted answer of paulvs, 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.")     }) } 
vote vote

69

Details

  • Xcode 10.2.1 (10E1001), Swift 5

Solution

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

Usage

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

58

You will need to use semaphores for this purpose.

 //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. 

Top 3 video Explaining Wait until swift for loop with asynchronous network requests finishes executing

Related QUESTION?