Cadena de múltiples solicitudes Alamofire

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

Digamos, por ejemplo, que quiero hacer lo siguiente:

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

Parece que el concepto de promesas puede ser una buena opción 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 menos no soy consciente de ello.

¿Cómo puedo lograr esta funcionalidad sin anidar múltiples métodos?

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

Envolver otras cosas asincrónicas en promesas funciona así:

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

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

Escribí una clase que maneja una cadena de solicitudes una por una.

Alamofire.Request una clase 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)") } } 

Lo importante es que le está diciendo al gerente que no ejecute la solicitud de inmediato

  let manager = Manager.sharedInstance manager.startRequestsImmediately = false 

Espero que ayude a alguien más

Actualización de 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 } 

Tienes múltiples opciones.


Opción 1 – Anidar llamadas

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

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 : división en métodos múltiples

 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 menos densa y divide las cosas en trozos más pequeños. Dependiendo de sus necesidades y de la complejidad de su análisis de respuesta, este puede ser un enfoque más legible.


Opción 3 : PromiseKit y Alamofire

Alamofire puede manejar esto bastante fácilmente sin tener que tirar de PromiseKit. Si realmente desea seguir esta ruta, puede usar el enfoque proporcionado por @mxcl.

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

Detalles

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

Muestra completa

Servicio de red

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

Func principal

 func run() { _ = firstly { return Promise { 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 in print("2 task finished") //print(error ?? "nil") //print(json ?? "nil") return Promise { $0.fulfill(Void())} } }.then {_ -> Promise in print("Update UI") return Promise { $0.fulfill(true)} }.then { previousResult -> Promise in print("previous result: \(previousResult)") return Promise { $0.fulfill(Void())} } } 

Resultado

enter image description here

Llamarse a sí mismo infinitamente y DEFINIR LA CONDICION FINAL. urlring para enlace API y Dictionary for json

Podemos construir el modelo de cola o delegar

  func getData(urlring : String , para : Dictionary) { 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 } }