Cadena de múltiples requestes Alamofire

Estoy buscando un buen patrón con el que puedo encadenar múltiples requestes HTTP. Quiero usar Swift, y preferiblemente Alamofire .

Por ejemplo, quiero hacer lo siguiente:

  1. Hacer una request PUT
  2. Hacer una request GET
  3. Recargar tabla con datos

Parece que el concepto de promises puede ser un buen ajuste para esto. PromiseKit podría ser una buena opción si pudiera hacer algo como esto:

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 } 

pero eso no es posible o al less no lo sé.

¿Cómo puedo lograr esta funcionalidad sin anidar varios methods?

Soy nuevo en iOS, así que tal vez hay algo más fundamental que me falta. Lo que he hecho en otros frameworks como Android es realizar estas operaciones en un process en segundo plano y hacer que las requestes sean sincrónicas. Pero Alamofire es inherentemente asíncrono , por lo que ese patrón no es una opción.

Envolver otras cosas asíncronas en promises funciona así:

 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) } } } } 

Edición: hoy en día, use: https://github.com/PromiseKit/Alamofire-

Tienes múltiples opciones


Opción 1 : llamadas de anidamiento

 func runTienetworkingRequests() { 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 } } } 

Este es definitivamente el enfoque que recomendaría. Anidar una llamada en otra es muy simple y es bastante fácil de seguir. También mantiene las cosas simples.


Opción 2 : dividirse en múltiples 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 } 

Esta opción es less densa y divide las partes en trozos más pequeños. Dependiendo de sus necesidades y la complejidad de su análisis de respuestas, esto puede ser un enfoque más legible.


Opción 3 : PromiseKit y Alamofire

Alamofire puede manejar esto fácilmente sin tener que recurrir a PromiseKit. Si realmente quieres seguir esta ruta, puedes usar el enfoque proporcionado por @mxcl.

Escribí una class que maneja una cadena de request una por una.

Alamofire.Request una class RequestChain que toma Alamofire.Request como parámetro

 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 } } } 

Y lo uso así

 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)") } } 

Importante es que le está diciendo al administrador que no ejecute la request de inmediato

  let manager = Manager.shanetworkingInstance manager.startRequestsImmediately = false 

Espero que ayude a otra persona

Actualización Swift 3.0

 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 } } } 

Ejemplo de uso 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 } 

Aquí hay otra forma de hacer esto (Swift 3, Alamofire 4.x) usando un 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 (ie 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 (ie 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") }) }) } }