Search code examples
iosswiftclosuresdispatch-queue

How to prevent Timing Problems When Using Closures?


I'm trying to find the sum of numbers, returned from different escaping closures. The sum to be returned in main thread.

import Foundation

var randomTime: Int {
  return Int.random(in: 0...1000)
}

func first(completion: @escaping (Int) -> Void) {
  DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(randomTime)) {
    completion(1)
  }
}

func second(completion: @escaping (Int) -> Void) {
  DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(randomTime)) {
    completion(2)
  }
}

func third(completion: @escaping (Int) -> Void) {
  DispatchQueue(label: "anotherThread").async {
    completion(3)
  }
}

func fourth(completion: @escaping (Int) -> Void) {
  DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(randomTime)) {
    completion(4)
}
}

Solution

  • If I got your question clear, You want to sum numbers but their values come at different times depending on a server response or some kind of delays. If that is the case then you have to use DispatchGroup

    Here is a helper function, It is calling your methods first(completion: @escaping (Int) -> Void)..... fourth(completion: @escaping (Int) -> Void) and notify main queue only when the last value is received. I put some comments on the code to help understand. Let me know if something is not clear.

    
        func computeOutPutAfterReceivingAllValues(completion: @escaping(_ sum: Int) -> Void) {
            // Make a dispatch group which will notify main queue after making sure that all requests have been proceed.
            let computeGroup = DispatchGroup()
            var allNumbers: [Int] = []
            computeGroup.enter()
            first { (firstNumber) in
                allNumbers.append(firstNumber)
                self.second(completion: { (secondNumber) in
                    allNumbers.append(secondNumber)
                    self.third(completion: { (thirdNumber) in
                        allNumbers.append(thirdNumber)
                        self.fourth(completion: { (fourthNumber) in
                            allNumbers.append(fourthNumber)
                             // IMPORTANT: Leave a group after the last call.
                            computeGroup.leave()
                        })
                    })
                })
    
            }
    
    
            // Notify Main queue and sum all your numbers
            computeGroup.notify(queue: .main) {
                /// Sum all your numbers in main queue
                let sum = allNumbers.reduce(0, +)
                completion(sum)
            }
        }
    
    

    Usage: You can test this in view didLoad.

     override func viewDidLoad() {
            super.viewDidLoad()
            computeOutPutAfterReceivingAllValues { (sum) in
                print("Here is the sum of all numbers: \(sum)")
            }
        }
    
    // Output on console
        Here is the sum of all numbers: 10