How to Handle Multiple Network Call in Alamofire

How to handle multiple network call in Alamofire

Your assessment is 100% correct. At the moment, the two options you laid out are really the only possible approaches. I agree with you that your second option is much better than the first given your use case.

If you wish to combine ReactiveCocoa with Alamofire, then that's certainly possible, but hasn't been done yet to my knowledge. You could also investigate whether PromiseKit would be able to offer some assistance, but it hasn't been glued together with Alamofire yet either. Trying to combine either of these libraries with the Alamofire response serializers will not be a trivial task by any means.

Switching gears a bit, I don't really think ReactiveCocoa or PromiseKit are very well suited for your use case since you aren't chaining service calls, you are running them in parallel. Additionally, you still need to run all your parsing logic and determine whether each one succeeded or failed and then update your application accordingly. What I'm getting at is that Option 2 is going to be your best bet by far unless you want to go to all the effort of combining PromiseKit or ReactiveCocoa with Alamofire's response serializers.

Here's what I would suggest to keep things less complicated.

import Foundation
import Alamofire

class ParallelServiceCaller {
var firstServiceCallComplete = false
var secondServiceCallComplete = false

func startServiceCalls() {
let firstRequest = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["first": "request"])
firstRequest.responseString { request, response, dataString, error in
self.firstServiceCallComplete = true
self.handleServiceCallCompletion()
}

let secondRequest = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["second": "request"])
secondRequest.responseString { request, response, dataString, error in
self.secondServiceCallComplete = true
self.handleServiceCallCompletion()
}
}

private func handleServiceCallCompletion() {
if self.firstServiceCallComplete && self.secondServiceCallComplete {
// Handle the fact that you're finished
}
}
}

The implementation is really clean and simple to follow. While I understand your desire to get rid of the completion flags and callback function, the other options such as ReactiveCocoa and/or PromiseKit are still going to have additional logic as well and may end up making things more complicated.

Another possible option is to use dispatch groups and semaphores, but that really adds complexity, but could get you much closer to a ReactiveCocoa or PromiseKit styled approach.

I hope that helps shed some light.

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

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.

Multiple calls simultaneously to an API in Alamofire, Swift

I solved the problem later by adding:

let configuration = URLSessionConfiguration.default
configuration.httpMaximumConnectionsPerHost = 10 // change the number according to need

Adding this will be able to send 10 requests simultaneously.

Chain multiple Alamofire requests

Wrapping other asynchronous stuff in promises works like this:

func myThingy() -> Promise<AnyObject> {
return Promise{ fulfill, reject in
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]).response { (_, _, data, error) in
if error == nil {
fulfill(data)
} else {
reject(error)
}
}
}
}

Edit: Nowadays, use: https://github.com/PromiseKit/Alamofire-

Wait for multiple Alamofire Requests to finish

I've made few changes in your code:

Network class:

func response(array: [JSON]){
print(array.count)
if(array.count == path){
self.delegate?.sendJson(array)
}
}

func getMultipleRequests(_ requests: [Int]) {
DispatchQueue.global(qos: .background).async {
let group = DispatchGroup()

var array: [JSON] = []

for request in requests {

group.enter()

self.getRequest(req: request, completion: { (json) in
array.append(json)
group.leave()
})
}

group.wait()
//this line below won't be called until all entries will leave the group
self.response(array: array)
}
}

func getRequest(req: Int, completion: @escaping (_ json: JSON) -> Void) {
path = req
let rot = Router(method: .get, path: req, parameters: nil)
Alamofire.request(rot)
.response { response in
print(response.request?.url! as Any)
// check for errors
guard response.error == nil else {
// got an error in getting the data, need to handle it
print(response.error!)
let errorJson: JSON = [ "Error" : "Can't get the data!"]
completion(errorJson)
return
}
// make sure we got some JSON since that's what we expect
guard (response.data?.base64EncodedString()) != nil else {
print("Error: \(String(describing: response.error))")
let errorJson: JSON = [ "Error" : "Can't get the data!"]
completion(errorJson)
return
}
guard response.response?.statusCode == 200 else{
let errorJson: JSON = [ "Error" : "Can't get the data!"]
completion(errorJson)
return
}
let json = JSON(data: response.data!)
// get and print the title
if json != nil{
completion(json)
} else {
let errorJson: JSON = [ "Error" : "Can't get the data!"]
completion(errorJson)
return
}
}
}

So the getRequest function now have a completion block that will return a json result off each request and the function getMultipleRequests that will receive a bunch of requests from anyone

This how you can use it

Your class, that calls refresh_fiks:

@objc func refresh_fiks(){
let network = Network()
network.delegate = self
self.teams = [[]]

network.getMultipleRequests([1,2])
}

Also, instead of using group.wait() you might need to use group.notify, it's better to notify that all entries leaved the group in specified queue, like the main:

group.notify(queue: DispatchQueue.main, execute: {
print("All Done")
self.response(array: array)
})

What to read about the DispatchGroups:

RayWenderlich

ALL ABOUT SWIFT

Multiple calls to a Alamofire request, get reference for each call

You need to keep a reference to the original request, should be the same for upload requests I think. Try the following:

func uploadDocWebservice(fileUrl: NSURL , progressView : PWProgressView , index : String , imageData : NSData? , name : String , mimeType : String , uploadType : String) -> Request? {
return Alamofire.upload ...
}

Then you can simply have an array of requests i.e:
var requests: [Request](). When calling Alamofire.upload / Alamofire .request - it returns a Request object.

You can then do:

var requests: [Request]()
let request = uploadDocWebservice(...)
requests.append(request)

Then you can just loop through the array and check whatever request you wish.

Making multiple requests with Alamofire depending on array of chunks

Thanks to Larme's comment I was able to find my mistake. When making request to API I was passing the decoded response to the completion closure. To fix this I had to declare an array of model let responses:[SomeModel] = [] and append the decoded result to it. I used let group = DispatchGroup() so I can wait the requests to execute and have my final array of results and then I used group.notify(queue: .main, execute: {completion(.success(responses))}) to return to the main queue and have my array of completed fetched data. This is now how my code looks like:

 private func request (_ idsDict: [String: [String]], _ idSchema: String, _ completion: @escaping APIListResponseClosure<SomeModel>) -> Void {
var responses: [SomeModel] = []
let group = DispatchGroup()
let chunks = transformEntitiesIdsToChunks(idsDict)
let decoder = JSONDecoder()

chunks.forEach {chunk in
group.enter()
let parameters = constructQueryParams(idsDict, chunk, idSchema, apiKey, clientId)
AF.request(baseURL, parameters: parameters).response { response in
switch response.result {
case .success(let data):
// some error handling for decoding and no data

responses.append(data.data)
group.leave()
case .failure(let error):
return completion(.failure(.APIError(error.localizedDescription)))
}
}
}
group.notify(queue: .main, execute: {
print("Ids are fetched")
completion(.success(responses))
})
}

Thanks again to Larme and I hope I helped someone else with this case.



Related Topics



Leave a reply



Submit