简体   繁体   English

链接多个 Alamofire 请求

[英]Chain multiple Alamofire requests

I'm looking for a good pattern with which I can chain multiple HTTP requests.我正在寻找一种可以链接多个 HTTP 请求的良好模式。 I want to use Swift, and preferrably Alamofire .我想使用 Swift,最好是Alamofire

Say, for example, I want to do the following:比如说,我想做以下事情:

  1. Make a PUT request发出 PUT 请求
  2. Make a GET request发出 GET 请求
  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:如果我可以做这样的事情, PromiseKit可能是一个不错的选择:

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.我是 iOS 新手,所以也许我缺少一些更基本的东西。 What I've done in other frameworks such as Android is to perform these operations in a background process and make the requests synchronous.我在Android等其他框架中所做的是在后台进程中执行这些操作并使请求同步。 But Alamofire is inherently asynchronous , so that pattern is not an option.但是Alamofire 本质上是异步的,所以这种模式不是一种选择。

Wrapping other asynchronous stuff in promises works like this:在 Promise 中包装其他异步内容的工作方式如下:

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-编辑:如今,使用: https : //github.com/PromiseKit/Alamofire-

I wrote a class which handles a chain of request one by one.我写了一个类,它一个接一个地处理请求链。

I created a class RequestChain wich takes Alamofire.Request as parameter我创建了一个类RequestChain至极需要Alamofire.Request作为参数

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斯威夫特 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
        }

    }
}

Usage Example Swift 3使用示例 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

        }

You have multiple options.您有多种选择。


Option 1 - Nesting Calls选项 1 - 嵌套调用

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选项 2 - 拆分为多种方法

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选项 3 - PromiseKit 和 Alamofire

Alamofire can handle this pretty easily without having to pull in PromiseKit. Alamofire 可以很容易地处理这个问题,而无需引入 PromiseKit。 If you really want to go this route, you can use the approach provided by @mxcl.如果你真的想走这条路,你可以使用@mxcl 提供的方法。

Here is another way to do this (Swift 3, Alamofire 4.x) using a DispatchGroup这是使用 DispatchGroup 执行此操作的另一种方法(Swift 3,Alamofire 4.x)

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

        })

    }
}

Details细节

  • Alamofire 4.7.2阿拉莫火 4.7.2
  • PromiseKit 6.3.4 PromiseKit 6.3.4
  • Xcode 9.4.1 Xcode 9.4.1
  • Swift 4.1斯威夫特 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结果

在此处输入图片说明

You can use the when method in PromiseKit to attach/append as many calls you want.您可以使用PromiseKitwhen方法来附加/附加PromiseKit调用。

Here's an example from PromiseKit docs :这是PromiseKit 文档中的一个示例:

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

It worked perfectly for me and it's a much cleaner solution.它对我来说非常有效,而且是一个更干净的解决方案。

Call itself infinitely and DEFINE END CONDITION.无限地调用自己并定义最终条件。 urlring for API link and Dictionary for json API链接的urlring和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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM