Meerdere Alamofire-verzoeken koppelen

Ik ben op zoek naar een goed patroon waarmee ik meerdere HTTP-verzoeken kan koppelen. Ik wil Swift gebruiken, en bij voorkeur Alamofire.

Stel bijvoorbeeld dat ik het volgende wil doen:

  1. Maak een PUT-verzoek
  2. Een GET-verzoek indienen
  3. Tabel opnieuw laden met gegevens

Het lijkt erop dat het concept van belofteshier goed bij past. PromiseKitzou een goede optie kunnen zijn als ik zoiets als dit zou kunnen doen:

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
}

maar dat is niet mogelijk of ik ben me er in ieder geval niet van bewust.

Hoe kan ik deze functionaliteit bereiken zonder meerdere methoden te nesten?

Ik ben nieuw bij iOS, dus misschien is er iets fundamentelers dat ik mis. Wat ik in andere frameworks zoals Android heb gedaan, is om deze bewerkingen in een achtergrondproces uit te voeren en de verzoeken synchroon te maken. Maar Alamofire is inherent asynchroon, dus dat patroon is geen optie.


Antwoord 1, autoriteit 100%

Andere asynchrone dingen in beloften verpakken werkt als volgt:

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

Bewerken: gebruik tegenwoordig: https://github.com/PromiseKit/Alamofire-


Antwoord 2, autoriteit 69%

Ik heb een klasse geschreven die een reeks verzoeken één voor één afhandelt.

Ik heb een klasse RequestChaingemaakt die Alamofire.Requestals parameter neemt

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

En ik gebruik het zo

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

Belangrijk is dat u de Manager vertelt het verzoek niet onmiddellijk uit te voeren

   let manager = Manager.sharedInstance
    manager.startRequestsImmediately = false

Hopelijk helpt het iemand anders

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

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

Antwoord 3, autoriteit 40%

Je hebt meerdere opties.


Optie 1– Oproepen nesten

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

Dit is zeker de aanpak die ik zou aanraden. Het nesten van de ene oproep in de andere is heel eenvoudig en vrij gemakkelijk te volgen. Het houdt de zaken ook eenvoudig.


Optie 2– Opsplitsen in meerdere methoden

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
}

Deze optie is minder compact en verdeelt dingen in kleinere stukken. Afhankelijk van uw behoeften en de complexiteit van het parseren van uw antwoorden, kan dit een meer leesbare benadering zijn.


Optie 3– PromiseKit en Alamofire

Alamofire kan dit vrij gemakkelijk aan zonder PromiseKit te hoeven gebruiken. Als je echt deze route wilt gaan, kun je de aanpak van @mxcl gebruiken.


Antwoord 4, autoriteit 16%

Hier is een andere manier om dit te doen (Swift 3, Alamofire 4.x) met behulp van een 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")
            })
        })
    }
}

Antwoord 5, autoriteit 2%

Details

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

Volledig voorbeeld

Netwerkservice

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

Hoofdfunctie

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

Resultaat

voer hier de afbeeldingsbeschrijving in


Antwoord 6

Je kunt de whenmethode in PromiseKitgebruiken om zoveel aanroepen toe te voegen als je wilt.

Hier is een voorbeeld van PromiseKitdocs:

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

Het werkte perfect voor mij en het is een veel schonere oplossing.


Antwoord 7

Noem zichzelf oneindig en DEFINIEER EINDE VOORWAARDE.
urlring voor API-link en Dictionary voor json

WE kunnen het wachtrijmodel construeren of delegeren

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

Other episodes