Search code examples
swiftchainingcompletionhandler

Working with Swift completion handlers for chained functions


I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.

class AirQualityProvider {
    var aBlock: ((Int?) -> Void)?

    func getAirQuality(completion: @escaping (Int?) -> Void) {
        aBlock = completion
        callAPI()
    }

    private func callAPI() {
        let data = Data()
        parseDataForAQI(data: data)
    }

    private func parseDataForAQI(data: Data) {

        for d in data {
            dosomeMath(d)
        }
    }


    private func dosomeMath(data: Int) {

        // HERE IS WHERE I WANT IT TO SUM UP ALL THE NUMBERS
        THEN ONLY RETURN ONE VALUE using a completion handler.
        Currently, it returns the average as it is being generated.
    }

Almost got it working with help to Alexander. The code Alexander supplied works perfectly, it is amazing. The issue is, when I run taskrunner inside alamofire it returns empty. Outside alamofire it works as usual. I need to run this inside alamofire.

func A(json : JSON){
    for (key,subJson) in json{
        if subJson["free"].doubleValue > 0.0 {
            func B(asset: subJson["asset"].stringValue, json: subJson)
        }
    }

    print(taskRunner.getResults())
}


func B(asset : String, json : JSON){

        //OUTSIDE ALAMOFIRE WORKS
            self.taskRunner.execute{
            return 100
        }

    Alamofire.request(url).responseJSON { response in

       //INSIDE ALAMOFIRE DOESN'T WORK. Returns []
            self.taskRunner.execute{
            return 100
        }

    }

}

Solution

  • I would use a dispatch queue to synchronize the aggregation of results (by synchronizing Array.append(_:) calls, and the subsequent reading of the array). Here's a simple example:

    import Dispatch
    import Foundation
    
    class ParallelTaskRunner<Result> {
        private var results = [Result]()
        private let group = DispatchGroup()
        private let resultAggregatorQueue = DispatchQueue(label: "Result Aggregator")
    
        func execute(_ closure: (@escaping (Result) -> Void) -> Void) {
            group.enter() // Register that a new task is in-flight
            closure { result in
                self.resultAggregatorQueue.sync { // Synchronize access to the array
                    self.results.append(result) // Record the result
                }
                self.group.leave() // This task is done
            }
        }
    
        func getResults() -> [Result] {
            group.wait() // Make sure all in-flight tasks are done
            return resultAggregatorQueue.sync { return results }
        }
    }
    
    
    
    
    let taskQueue = DispatchQueue(label: "Task Queue", attributes: .concurrent)
    let taskRunner = ParallelTaskRunner<Int>()
    for i in 0...100 {
        taskRunner.execute { completionHandler in 
            taskQueue.async { // Simulated async computation
                let randomTime = 3.0
                print("Sleeping for \(randomTime)")
                Thread.sleep(forTimeInterval: randomTime) // Simulates intesnive computation
                let result = i // Simulate a result
                completionHandler(result)
            }
        }
    }
    print(taskRunner.getResults()) // Oh look, all the results are here! :D