Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple calls using Alamofire to different APIs - how to save results to an array?

I'm developing an app that fetches data from few different APIs using Alamofire (each call is done using a function). Then I have to collect all of the results (Double type in my case) to one array to calculate the average.

As long as Alamofire uses asynchronous calls, it is impossible to simply append new value to an array from inside of call.

Here is a function that calls each of functions responsible for fetching data by Alamofire:

func collectData() {
   fetchFromFirstAPI()
   fetchFromSecondAPI()  //etc.
}

And here is an example of one of these functions:

func fetchFromFirstAPI() {
   let APIKey = "XXXXXXXXX"
   let APIURL = "http://urlapi.com/api" as URLConvertible

   let parameters: Parameters = ["APPKEY": APIKey]

   Alamofire.request(APIURL, method: .get, parameters: parameters, encoding: URLEncoding.default).validate().responseJSON { response in
        switch response.result {
        case .success(let data):
            let json = JSON(data)
            if let result = json["main"]["value"].double {
                myArray.append(result)
            } else {
                print("error")
            }
        case .failure:
            print("error")
        }
   }
}

And the array:

var myArray: [Double] = []

How to deal with it?

like image 489
mrpapa Avatar asked Dec 05 '16 01:12

mrpapa


1 Answers

You said:

As long as Alamofire uses asynchronous calls, it is impossible to simply append new value to an array from inside of call.

You can actually go ahead and append items to the array inside the completion handlers. And because Alamofire calls its completion handlers on the main queue by default, no further synchronization is needed. And you can use dispatch groups to know when all the requests are done. First, I'd give my methods completion handlers so I know when they're done, e.g.:

func fetchFromFirstAPI(completionHandler: @escaping (Double?, Error?) -> Void) {
    let APIKey = "XXXXXXXXX"
    let APIURL = "http://urlapi.com/api"

    let parameters: Parameters = ["APPKEY": APIKey]

    Alamofire.request(APIURL, parameters: parameters).validate().responseJSON { response in
        switch response.result {
        case .success(let data):
            let json = JSON(data)
            if let result = json["main"]["value"].double {
                completionHandler(result, nil)
            } else {
                completionHandler(nil, FetchError.valueNotFound)
            }
        case .failure(let error):
            completionHandler(nil, error)
        }
    }
}

Where

enum FetchError: Error {
    case valueNotFound
}

And you can then do something like:

func performRequestsAndAverageResults(completionHandler: @escaping (Double?) -> ()) {
    var values = [Double]()
    let group = DispatchGroup()

    group.enter()
    fetchFromFirstAPI { value, error in
        defer { group.leave() }
        if let value = value { values.append(value) }
    }

    group.enter()
    fetchFromSecondAPI { value, error in
        defer { group.leave() }
        if let value = value { values.append(value) }
    }

    group.notify(queue: .main) {
        completionHandler(self.average(values))
    }
}

func average<T: FloatingPoint>(_ values: [T]) -> T? {
    guard values.count > 0 else { return nil }

    let sum = values.reduce(0) { $0 + $1 }
    return sum / T(values.count)
}

Now, you may want to handle errors differently, but this should illustrate the basic idea: Just append the results in the completion handlers and then use dispatch groups to know when all the requests are done, and in the notify closure, perform whatever calculations you want.

Clearly, the above code doesn't care what order these asynchronous tasks finish. If you did care, you'd need to tweak this a little (e.g. save the results to a dictionary and then build a sorted array). But if you're just averaging the results, order doesn't matter and the above is fairly simple.

like image 187
Rob Avatar answered Oct 23 '22 22:10

Rob