Chain multiple Alamofire requests

IosSwiftAlamofirePromisekit

Ios Problem Overview


I'm looking for a good pattern with which I can chain multiple HTTP requests. I want to use Swift, and preferrably Alamofire.

Say, for example, I want to do the following:

  1. Make a PUT request
  2. Make a GET request
  3. Reload table with data

It seems that the concept of promises may be a good fit for this. PromiseKit could be a good option if I could do something like this:

NSURLConnection.promise(
    Alamofire.request(
        Router.Put(url: "http://httbin.org/put")
    )
).then { (request, response, data, error) in
    Alamofire.request(
        Router.Get(url: "http://httbin.org/get")
    )   
}.then { (request, response, data, error) in
    // Process data
}.then { () -> () in
    // Reload table
}

but that's not possible or at least I'm not aware of it.

How can I achieve this functionality without nesting multiple methods?

I'm new to iOS so maybe there's something more fundamental that I'm missing. What I've done in other frameworks such as Android is to perform these operations in a background process and make the requests synchronous. But Alamofire is inherently asynchronous, so that pattern is not an option.

Ios Solutions


Solution 1 - Ios

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-

Solution 2 - Ios

I wrote a class which handles a chain of request one by one.

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

Swift 3.0 Update

class RequestChain {
    typealias CompletionHandler = (_ success:Bool, _ errorResult:ErrorResult?) -> Void
    
    struct ErrorResult {
        let request:DataRequest?
        let error:Error?
    }
    
    fileprivate var requests:[DataRequest] = []
    
    init(requests:[DataRequest]) {
        self.requests = requests
    }
    
    func start(_ completionHandler:@escaping CompletionHandler) {
        if let request = requests.first {
            request.response(completionHandler: { (response:DefaultDataResponse) in
                if let error = response.error {
                    completionHandler(false, ErrorResult(request: request, error: error))
                    return
                }
                
                self.requests.removeFirst()
                self.start(completionHandler)
            })
            request.resume()
        }else {
            completionHandler(true, nil)
            return
        }
        
    }
}

Usage Example Swift 3

/// set Alamofire default manager to start request immediatly to false
        SessionManager.default.startRequestsImmediately = false
        let firstRequest = Alamofire.request("https://httpbin.org/get")
        let secondRequest = Alamofire.request("https://httpbin.org/get")
        
        let chain = RequestChain(requests: [firstRequest, secondRequest])
        chain.start { (done, error) in
            
        }

Solution 3 - Ios

You have multiple options.


Option 1 - Nesting Calls

func runTieredRequests() {
    let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
    putRequest.response { putRequest, putResponse, putData, putError in
        let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
        getRequest.response { getRequest, getResponse, getData, getError in
            // Process data
            // Reload table
        }
    }
}

This is definitely the approach I would recommend. Nesting one call into another is very simple and is pretty easy to follow. It also keeps things simple.


Option 2 - Splitting into Multiple Methods

func runPutRequest() {
    let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
    putRequest.response { [weak self] putRequest, putResponse, putData, putError in
        if let strongSelf = self {
            // Probably store some data
            strongSelf.runGetRequest()
        }
    }
}

func runGetRequest() {
    let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
    getRequest.response { [weak self] getRequest, getResponse, getData, getError in
        if let strongSelf = self {
            // Probably store more data
            strongSelf.processResponse()
        }
    }
}

func processResponse() {
    // Process that data
}

func reloadData() {
    // Reload that data
}

This option is less dense and splits things up into smaller chunks. Depending on your needs and the complexity of your response parsing, this may be a more readable approach.


Option 3 - PromiseKit and Alamofire

Alamofire can handle this pretty easily without having to pull in PromiseKit. If you really want to go this route, you can use the approach provided by @mxcl.

Solution 4 - Ios

Here is another way to do this (Swift 3, Alamofire 4.x) using a DispatchGroup

import Alamofire

    struct SequentialRequest {

        static func fetchData() {
    
            let authRequestGroup =  DispatchGroup()
            let requestGroup = DispatchGroup()
            var results = [String: String]()
    
            //First request - this would be the authentication request
            authRequestGroup.enter()
            Alamofire.request("http://httpbin.org/get").responseData { response in
            print("DEBUG: FIRST Request")
            results["FIRST"] = response.result.description
        
            if response.result.isSuccess { //Authentication successful, you may use your own tests to confirm that authentication was successful
            
                authRequestGroup.enter() //request for data behind authentication
                Alamofire.request("http://httpbin.org/get").responseData { response in
                    print("DEBUG: SECOND Request")
                    results["SECOND"] = response.result.description
                
                    authRequestGroup.leave()
                }
            
                authRequestGroup.enter() //request for data behind authentication
                Alamofire.request("http://httpbin.org/get").responseData { response in
                    print("DEBUG: THIRD Request")
                    results["THIRD"] = response.result.description
                
                    authRequestGroup.leave()
                }
            }
        
            authRequestGroup.leave()
        
        }
    
    
        //This only gets executed once all the requests in the authRequestGroup are done (i.e. FIRST, SECOND AND THIRD requests)
        authRequestGroup.notify(queue: DispatchQueue.main, execute: {
        
            // Here you can perform additional request that depends on data fetched from the FIRST, SECOND or THIRD requests
        
            requestGroup.enter()
            Alamofire.request("http://httpbin.org/get").responseData { response in
                print("DEBUG: FOURTH Request")
                results["FOURTH"] = response.result.description
            
                requestGroup.leave()
            }
        
        
            //Note: Any code placed here will be executed before the FORTH request completes! To execute code after the FOURTH request, we need the request requestGroup.notify like below
            print("This gets executed before the FOURTH request completes")
                    
            //This only gets executed once all the requests in the requestGroup are done (i.e. FORTH request)
            requestGroup.notify(queue: DispatchQueue.main, execute: {
            
                //Here, you can update the UI, HUD and turn off the network activity indicator
            
                for (request, result) in results {
                    print("\(request): \(result)")
                }
            
                print("DEBUG: all Done")
            })
        
        })
    
    }
}

Solution 5 - Ios

Details

  • Alamofire 4.7.2
  • PromiseKit 6.3.4
  • Xcode 9.4.1
  • Swift 4.1

Full Sample

> NetworkService

import Foundation
import Alamofire
import PromiseKit

class NetworkService {
    
    static fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
    
    fileprivate class func make(request: DataRequest) -> Promise <(json: [String: Any]?, error: Error?)> {
        return Promise <(json: [String: Any]?, error: Error?)> { seal in
            request.responseJSON(queue: queue) { response in
                
                // print(response.request ?? "nil")  // original URL request
                // print(response.response ?? "nil") // HTTP URL response
                // print(response.data ?? "nil")     // server data
                //print(response.result ?? "nil")   // result of response serialization
                
                switch response.result {
                case .failure(let error):
                    DispatchQueue.main.async {
                        seal.fulfill((nil, error))
                    }
                    
                case .success(let data):
                    DispatchQueue.main.async {
                        seal.fulfill(((data as? [String: Any]) ?? [:], nil))
                    }
                }
            }
        }
    }
    
    class func searchRequest(term: String) -> Promise<(json: [String: Any]?, error: Error?)>{
        let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
        return make(request: request)
    }
}

> Main func

func run() {
    _ = firstly {
        return Promise<Void> { seal in
            DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
                print("1 task finished")
                DispatchQueue.main.async {
                    seal.fulfill(Void())
                }
            }
        }
        }.then {
            return NetworkService.searchRequest(term: "John").then { json, error -> Promise<Void> in
                print("2 task finished")
                //print(error ?? "nil")
                //print(json ?? "nil")
                return Promise { $0.fulfill(Void())}
            }
        }.then {_ -> Promise<Bool> in
            print("Update UI")
            return Promise { $0.fulfill(true)}
        }.then { previousResult -> Promise<Void> in
            print("previous result: \(previousResult)")
            return Promise { $0.fulfill(Void())}
    }
}

Result

enter image description here

Solution 6 - Ios

You can use the when method in PromiseKit to attach/append as many calls you want.

Here's an example from PromiseKit docs:

firstly {
    when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
    //…
}

It worked perfectly for me and it's a much cleaner solution.

Solution 7 - Ios

Call itself infinitely and DEFINE END CONDITION. urlring for API link and Dictionary for json

WE may construct the queue model or delegate

 func getData(urlring : String  , para :  Dictionary<String, String>) {
        
    if intCount > 0 {
        
        Alamofire.request( urlring,method: .post, parameters: para , encoding: JSONEncoding.default, headers: nil) .validate()
            .downloadProgress {_ in
            }
            .responseSwiftyJSON {
                dataResponse in
                switch dataResponse.result {
                case .success(let json):
                    print(json)
                    let loginStatus : String = json["login_status"].stringValue
                    print(loginStatus)
                    if  loginStatus == "Y" {
                        print("go this")
                        print("login success : int \(self.intCount)")
                        
                        self.intCount-=1
                        self.getData(urlring: urlring , para : para)
                    }
                case .failure(let err) :
                    print(err.localizedDescription)
                }
        }
    }else{
       //end condition workout
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjlhonoraView Question on Stackoverflow
Solution 1 - IosmxclView Answer on Stackoverflow
Solution 2 - IosEikeView Answer on Stackoverflow
Solution 3 - IoscnoonView Answer on Stackoverflow
Solution 4 - IosEtienne BeauleView Answer on Stackoverflow
Solution 5 - IosVasily BodnarchukView Answer on Stackoverflow
Solution 6 - IosYonathan GoriachnickView Answer on Stackoverflow
Solution 7 - IosRaju yourPepeView Answer on Stackoverflow