Cadena de múltiples solicitudes Alamofire

Estoy buscando un buen patrón con el que puedo cadena de múltiples peticiones HTTP. Quiero usar Swift, y de preferencia Alamofire.

Decir, por ejemplo, quiero hacer lo siguiente:

  1. Hacer una solicitud PUT
  2. Hacer una petición GET
  3. Volver a cargar la tabla con los datos

Parece que el concepto de promesas puede ser una buena opción para esto. PromiseKit podría ser una buena opción si yo podría 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 por lo menos no soy consciente de ello.

¿Cómo puedo conseguir esta funcionalidad sin anidar varios métodos?

Soy nuevo en iOS, así que tal vez hay algo más fundamental de que me estoy perdiendo. Lo he hecho en otros frameworks como Android es para realizar estas operaciones en un proceso en segundo plano y hacer las peticiones síncronas. Pero Alamofire es inherentemente asincrónica, por lo que el patrón no es una opción.

  • No he utilizado PromiseKit, pero la alternativa sería el uso de AFNetworking del AFHTTPRequestOperation, que no se puede poner en un NSOperationQueue. Se puede establecer que las operaciones sólo se inicia como otras operaciones han terminado.
  • Usted debe ser capaz de utilizar PromiseKit aunque usted tendrá que proporcionar su propia manutención para ella, la forma más obvia sería como una extensión AlamoFire.request Checkout lo que han hecho para NSURLConnection y usar eso como un modelo.
  • Usted podría utilizar ReactiveCocoa en lugar de PromiseKit. ReactiveCocoa puede ser visto como un superconjunto de PromiseKit, ya que proporciona mucha más funcionalidad, puede ser utilizado en muchos más lugares, simplifica su estructura de código y mucho más
InformationsquelleAutor jlhonora | 2015-02-20

6 Kommentare

  1. 44

    Envolver otros asincrónica cosas en las promesas de obras como esta:

    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: Hoy en día, el uso de: https://github.com/PromiseKit/Alamofire-

    • puede dar un ejemplo de un caso de uso? tal vez la implementación de las solicitudes publicado en la pregunta?
  2. 26

    Escribí una clase que maneja una cadena de solicitar uno por uno.

    He creado una clase RequestChain que se 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 yo lo uso como este

    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 es que usted está diciendo el Gerente de la no ejecución de la solicitud de inmediato

        let manager = Manager.sharedInstance
        manager.startRequestsImmediately = false

    Esperamos que ayude a alguien más

    Swift 3.0 Actualización

    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 De 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
    
            }
    • Esto es muy fresco y fija un problema que estaba teniendo muy elegante. Ahora se quejan cuando se ejecuta en Swift 3 solicitud.respuesta(completionHandler: { (_, _, _, error) dando un error «No se puede llamar valor de la no-tipo de función HTTPURLResponse?». Gracias.
    • Hey @iphaaw he actualizado el post con swift 3.0 código.
    • gracias por la actualización
    • Hy @Eike, podría tal vez agregar un ejemplo de cómo la swift3 clase se utiliza entonces? Gracias!
    • No hay problema. He actualizado el ejemplo de abajo
    • La mejor Respuesta, sin duda la mayoría de la programación orientada a objetos. GRACIAS 🙂
    • Mejor método, pero yo intente agregar en swift 4 y caer siempre en la solicitud.respuesta(completionHandler: { (_, _, _, error). mismo problema que iPhaaw cara anterior.
    • ¿Cómo podría obtener algunos datos sacados de la primera respuesta a la segunda solicitud?
    • ¿Cómo puedo obtener el error de asignación y de respuesta para cada solicitud por separado? Es eso posible?
    • Gracias por la respuesta, realmente me ayudó. Pero tengo otro problema, cuando todas las solicitudes acabar he actualizado mi interfaz de usuario, pero no soy capaz de hacer otra alamofire solicitud.

  3. 17

    Usted tiene varias opciones.


    Opción 1 De Anidación De Las 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
            }
        }
    }

    Definitivamente este es el enfoque que yo recomendaría. La anidación de una llamada a otra es muy simple y es bastante fácil de seguir. También mantiene las cosas simples.


    Opción 2 – la División en Múltiples Métodos

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


    Opción 3 – PromiseKit y Alamofire

    Alamofire puede manejar esto con bastante facilidad sin tener que tirar en PromiseKit. Si realmente quieres ir a esta ruta, usted puede utilizar el enfoque proporcionado por @mxcl.

    • Opción 3 podría ser complementado con @mxcl la respuesta
    • Tienes toda la razón…he actualizado la respuesta en consecuencia.
    • Las dos primeras opciones implican la anidación de que es lo que promete están diseñados para evitar. Así que no estoy seguro de que tiene sentido decir que Alamofire puede manejar bastante bien. No están realmente diciendo que la anidación no es un problema?
    • Yo no veo por ninguna parte en mi respuesta que dice Alamofire se encarga de esto «bastante bien». Yo simplemente señaló tres opciones diferentes para la realización de la tarea. Al no ser un experto en PromiseKit, me imagino que ofrecen un par de opciones de uso de Alamofire sólo, con un tercio de aplazar a PromiseKit directamente. El encadenamiento de dos solicitudes juntos se puede hacer directamente con Alamofire. Más de dos y empieza a ser bastante difícil de manejar. Esto es algo de lo que estamos seguro que va a investigar en el futuro. 👍🏼
    • Pero si haces muchas llamadas en un para, ¿cómo puedo saber cuando la última llamada es terminada ?
    • La opción 1 no podría hacer lo que se desea… tan pronto Como el anidada Alamofire solicitud get comienza, la función devuelve.

  4. 6

    Aquí es otra manera de hacer esto (Swift 3, Alamofire 4.x) utilizando 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 (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")
                })
    
            })
    
        }
    }
    • Esto se ve muy elegante, pero, ¿cómo pueden los datos a ser recogidos dentro de la notify llamar?
    • Simplemente declarar la variable que va a contener los datos antes de la solicitud de llamadas, rellenar con cada solicitud y hacer algo con la variable en la notificación de llamada (se rellena a partir de los datos de la solicitud en ese momento). BTW, voy a actualizar el código de la respuesta mañana (he encontrado una manera más confiable para la conexión en cadena de las solicitudes)…
    • Yo solía PromiseKit en el pasado a la cadena de dichas solicitudes. Me parece una aplicación muy útil marco de modo que usted puede ser que desee comprobar hacia fuera.
    • Acaba de actualizar la respuesta con más detalle (y complejo) ejemplo. Me puse a pensar en PromiseKit pero sentí que era la adición de demasiada complejidad, simplemente, con el propósito de hacer encadenados peticiones…
    • Esto es lo que Envío a los grupos creados. Esta es la mejor respuesta porque enseña a un concepto útil para más adelante (cuando usted consigue en serio multithreading)
    • No podía conseguir que esto funcione, con tres Alamofire peticiones…la notifica corrió demasiado pronto.
    • Cómo extraer todos los datos resultantes de notificar a llamar?
    • Kiran: ahora prefiero PromiseKit más de dispatchGroup. Hay un poco de una curva de aprendizaje con PromiseKit pero es la manera más fácil de manejar, una vez que empiece a tener un montón de peticiones para la conexión en cadena de…

  5. 0

    Detalles

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

    Total De La Muestra

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

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

    Resultado

    Cadena de múltiples solicitudes Alamofire

  6. -1

    Se llame a sí mismo infinitamente y DEFINIR la CONDICIÓN FINAL.
    urlring para la API de enlace y el Diccionario de json

    PODEMOS construir el modelo cola o delegado

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

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea