How is it possible to perform multiple Alamofire requests that are finished one after another?
The issue is just the same as in the related question you posed: the operation dependencies are on finishing an operation, as documented, but you have written code where the operation exits after asynchronously dispatching a request for future execution (the operations you created and added to a queue will finish in the order set by their dependencies, but the requests will be fired concurrently by the NSURLSession underlying Alamofire).
If you need serial execution, you can for instance do the following:
// you should create an operation queue, not use OperationQueue.main here –
// synchronous network IO that would end up waiting on main queue is a real bad idea.
let operationQueue = OperationQueue()
let timeout:TimeInterval = 30.0
for operationNumber in 0..<4 {
let operation = BlockOperation {
let s = DispatchSemaphore(value: 0)
self.performAlamofireRequest(operationNumber) { number in
// do stuff with the response.
s.signal()
}
// the timeout here is really an extra safety measure – the request itself should time out and end up firing the completion handler.
s.wait(timeout: DispatchTime(DispatchTime.now, Int64(timeout * Double(NSEC_PER_SEC))))
}
operationQueue.addOperation(operation)
}
Various other solutions are discussed in connection to this question, arguably a duplicate. There's also Alamofire-Synchronous.
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
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-
Alamofire number of requests one after another
Ok I ended up writing my own implementation.
I created a class RequestChain
wich takes Alamofire.Request
as parameter
class RequestChain {
typealias CompletionHandler = (success:Bool, errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:Request?
let error:ErrorType?
}
private var requests:[Request] = []
init(requests:[Request]) {
self.requests = requests
}
func start(completionHandler:CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (_, _, _, error) in
if error != nil {
completionHandler(success: false, errorResult: ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(success: true, errorResult: nil)
return
}
}
}
And I use it like this
let r1 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("1")
}
let r2 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("2")
}
let r3 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("3")
}
let chain = RequestChain(requests: [r1,r2,r3])
chain.start { (success, errorResult) in
if success {
print("all have been success")
}else {
print("failed with error \(errorResult?.error) for request \(errorResult?.request)")
}
}
Importent is that you are telling the Manager to not execute the request immediately
let manager = Manager.sharedInstance
manager.startRequestsImmediately = false
Hope it will help someone else
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.
Wait for multiple Alamofire requests to complete before continuing?
use disptach_group_t
Here is Example
dispatch_group_t group = dispatch_group_create();
__weak MainViewControllerSupplier * weakSelf = self;
dispatch_group_enter(group);
[self showHUD];
[[DataManager sharedManager] getCategoriesWithSuccessBlock:^(NSArray *categories) {
weakSelf.arrCategories = categories;
dispatch_group_leave(group);
// NSLog(@"response category= %@",categories);
} failureBlock:^(NSError *error) {
NSLog(@"response category = %@",error);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[DataManager sharedManager] getRegionsWithSuccessBlock:^(NSArray *regions) {
weakSelf.arrRegions = regions;
dispatch_group_leave(group);
} failureBlock:^(id error) {
NSLog(@"response region = %@",error);
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self hideHUD];
});
// All task completed
});
Alamofire multiple requests iteration
If this is not running, you could be deadlocking if you use dispatch_group_wait
on the main thread, thereby blocking that thread, and preventing Alamofire from running any of its completion handlers (which also require the main thread). This is solved (assuming you are, indeed, using dispatch_group_wait
), by replacing that with dispatch_group_notify
.
Thus:
let group = dispatch_group_create()
for d in data {
// I enter the group
dispatch_group_enter(group)
dataService.getPoints(d.point)) { additionalPoints, error in
defer { dispatch_group_leave(group) }
guard let let additionalPoints = additionalPoints else {
print(error)
return
}
points.append(additionalPoints)
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
// go to the next iteration here
}
Where:
func getPoints(point: WhateverThisIs, completionHandler: (JSON?, NSError?) -> ()) {
let headers = [
"Authorization": "Bearer \(token)"
]
Alamofire.request(.GET, url, headers: headers)
.responseJSON { response in
switch response.result {
case .Success:
let json = JSON(data: response.data!)
completionHandler(json, nil)
case .Failure(let error):
completionHandler(nil, error)
}
}
}
Now, I don't know what your various parameter types were, so I was left to guess, so don't get lost in that. But the key is that (a) you should make sure that all paths within your Alamofire method will call the completion handler; and (b) the caller should use dispatch_group_notify
rather than dispatch_group_wait
, avoiding the blocking of any threads.
Note, in order to make the completion handler callable even if the network request failed, I had to make the parameter to that closure optional. And while I was there, I added an optional error parameter, too.
A few unrelated changes included in the above sample:
I'd suggest using a different variable name for the parameter of your closure. The
points.append(points)
in your original code snippet suggests some confusion between yourpoints
array and thepoints
that is passed back in the closure.You don't have to set the
Content-Type
header, as Alamofire does that for you.I didn't change it above, but it is inefficient to use
responseJSON
(which usesNSJSONSerialization
to parse the JSON) and then use SwiftyJSON to parse the rawdata
withNSJSONSerialization
again. Personally, I don't bother with SwiftyJSON, but if you want to use it, I'd suggest use Alamofire'sresponse
method ratherresponseJSON
. There's no point in parsing the JSON twice.
Alamofire callback after multiple requests finish
I finally wrapped my head around GCD handling multiple concurrent tasks on this post Dispatch Groups suggested by @GoodSp33d
Related Topics
Difference Between Static Enum and Static Struct
Realm Swift: Always Put Nil Values Last in Sort
Same Class Extension in Two Different Modules
Correct Way to Use Nwconnection for Long-Running Tcp Socket
Passing a Variable Through a Segue? Xcode 8 Swift 3
How to Detect the 2D Images Using Arkit and Realitykit
How to Fix Memory Leaks in iOS Applications
Button State Activates on Wrong Cells
Inline Kvo of a Property in Another View Controller
Use Quick Look Inside a Swift Cocoa Application to Preview Audio Files
Swift Skshapenode Shapewithsplinepoints
Changing Texteditor Background Color in Swiftui for MACos
Swift Unsafemutablepointer: Must I Call Deinitialize Before Deallocate
Why Can't Swift Automatically Convert a Generic Type Parameter to Its Superclass
Swift Error Comparing Two Arrays of Optionals
Swift: Label Text --> "Fatal Error: Unexpectedly Found Nil While Unwrapping an Optional Value"