【问题标题】:Chain multiple Alamofire requests链接多个 Alamofire 请求
【发布时间】:2015-04-22 11:41:22
【问题描述】:

我正在寻找一种可以链接多个 HTTP 请求的良好模式。我想使用 Swift,最好是Alamofire

比如说,我想做以下事情:

  1. 发出 PUT 请求
  2. 发出 GET 请求
  3. 用数据重新加载表

看来promises 的概念可能很适合这个。如果我可以这样做,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
}

但这是不可能的,或者至少我不知道。

如何在不嵌套多个方法的情况下实现此功能?

我是 iOS 新手,所以也许我缺少一些更基本的东西。我在其他框架(例如 Android)中所做的是在后台进程中执行这些操作并使请求同步。但是Alamofire is inherently asynchronous,所以这种模式不是一种选择。

【问题讨论】:

  • 我没有使用过 PromiseKit,但替代方法是使用 AFNetworking 的 AFHTTPRequestOperation,您可以将其放入 NSOperationQueue。您可以将操作设置为仅在其他操作完成时开始。
  • 您应该能够使用PromiseKit,尽管您必须自己提供支持,但显而易见的方法是作为AlamoFire.request 的扩展查看他们为@ 所做的事情987654331@ 并将其用作模型。
  • 你可以使用 ReactiveCocoa 代替 PromiseKit。 ReactiveCocoa 可以看作是 PromiseKit 的超集,因为它提供了更多的功能,可以在更多的地方使用,简化你的代码结构等等

标签: ios swift alamofire promisekit


【解决方案1】:

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

编辑:现在,使用:https://github.com/PromiseKit/Alamofire-

【讨论】:

  • 你能举一个用例的例子吗?也许实现问题中发布的请求?
  • 如果下一个请求输入需要上一个请求的响应,我们如何处理链接?
【解决方案2】:

我写了一个类来逐个处理请求链。

我创建了一个类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
        }

    }
}

我是这样使用的

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


}

重要的是你告诉经理不要立即执行请求

    let manager = Manager.sharedInstance
    manager.startRequestsImmediately = false

希望对别人有所帮助

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
        }

    }
}

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

        }

【讨论】:

  • 这很酷,并且非常优雅地解决了我遇到的问题。它现在在 Swift 3 request.response(completionHandler: { (_, _, _, error) 中运行时抱怨“无法调用非函数类型 HTTPURLResponse 的值?”。谢谢。
  • 嗨@Eike,您能否添加一个示例来说明如何使用 swift3 类?谢谢!
  • 最佳答案,绝对是大多数 OOP。谢谢:)
  • 最好的方法,但我尝试在 swift 4 中添加它,它总是落在 request.response(completionHandler: { (_, _, _, error)。与 iPhaaw 之前面临的相同问题。跨度>
  • 如何从第一个响应中提取一些数据到第二个请求中?
【解决方案3】:

您有多种选择。


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

这绝对是我推荐的方法。将一个调用嵌套到另一个调用中非常简单,并且很容易理解。它还使事情变得简单。


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

此选项不那么密集,并将事物分成更小的块。根据您的需求和响应解析的复杂性,这可能是一种更易读的方法。


选项 3 - PromiseKit 和 Alamofire

Alamofire 可以很容易地处理这个问题,而无需引入 PromiseKit。如果你真的想走这条路,可以使用@mxcl提供的方法。

【讨论】:

  • 选项 3 可以用@mxcl 的回答来补充
  • 您的前两个选项涉及嵌套,这是 promise 旨在避免的。所以我不确定说 Alamofire 可以很好地处理这个问题是否有意义。你不是真的说嵌套不是问题吗?
  • 我在我的回答中没有看到任何地方说 Alamofire 处理这个“非常好”。我只是指出了完成任务的三个不同选项。不是 PromiseKit 的专家,我想我会提供几个仅使用 Alamofire 的选项,第三个选项直接推迟到 PromiseKit。使用 Alamofire 可以轻松地将两个请求链接在一起。超过两个,它开始变得非常笨拙。这是我们将来肯定要调查的事情。 ??
  • 但是如果你在一个for中调用了很多次,我怎么知道最后一次调用是什么时候完成的呢?
  • 选项 1 可能无法达到预期效果...一旦嵌套的 Alamofire get 请求开始,函数就会返回。
【解决方案4】:

这是使用 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")
            })

        })

    }
}

【讨论】:

  • 这看起来很优雅,但是如何在notify调用中收集数据呢?
  • 只需在请求调用之前声明将保存数据的变量,用每个请求填充它并在通知调用中对变量做一些事情(它将从当时的请求数据中填充) .顺便说一句,我明天会更新答案中的代码(我找到了一种更可靠的方式来菊花链请求)......
  • 我过去使用PromiseKit 链接此类请求。我发现它是一个非常方便的框架,因此您可能想查看一下。
  • 这就是调度组的用途。这是更好的答案,因为它教给你一个非常有用的概念,供以后使用(当你进入严肃的多线程时)
  • 我无法让它处理三个 Alamofire 请求...通知运行得太快了。
【解决方案5】:

详情

  • Alamofire 4.7.2
  • PromiseKit 6.3.4
  • Xcode 9.4.1
  • 斯威夫特 4.1

完整样本

网络服务

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

结果

【讨论】:

    【解决方案6】:

    您可以使用PromiseKit 中的when 方法来附加/附加任意数量的调用。

    这是来自PromiseKitdocs的示例:

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

    它对我来说非常有效,而且它是一个更清洁的解决方案。

    【讨论】:

      【解决方案7】:

      无限调用自身并定义结束条件。 API 链接的 urlring 和 json 的字典

      我们可以构建队列模型或委托

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-10-26
        • 1970-01-01
        • 1970-01-01
        • 2016-12-02
        相关资源
        最近更新 更多