2015-02-20 2 views
39

Я ищу хороший шаблон, с которым я могу связать несколько HTTP-запросов. Я хочу использовать Swift, и желательно Alamofire.Цепочки множественных запросов Alamofire

Скажем, к примеру, я хочу сделать следующее:

  1. Сделать запрос PUT
  2. сделать запрос GET
  3. Reload таблица с данными

Кажется, что концепция 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, так что шаблон не является вариантом.

+0

Я не использовал PromiseKit, но альтернативы можно было бы использовать AFNetworking-х [ 'AFHTTPRequestOperation'] (HTTP://cocoadocs.org/docsets/AFNetworking/2.4.1/Classes/AFHTTPRequestOperation.html), который вы можете добавить в 'NSOperationQueue'. Вы можете установить, чтобы операции запускались только по завершении других операций. –

+0

Вы должны иметь возможность использовать 'PromiseKit', хотя вам придется предоставить свою собственную поддержку, очевидный путь будет в качестве расширения для« AlamoFire.request »Checkout, что они сделали для« NSURLConnection », и использовать это в качестве модели. –

+0

Вы можете использовать ReactiveCocoa вместо PromiseKit. ReactiveCocoa можно рассматривать как надмножество PromiseKit, поскольку он обеспечивает гораздо большую функциональность, может использоваться во многих других местах, упрощает структуру кода и многое другое – gkaimakas

ответ

40

Обертывания другого асинхронного материала обещаний работает следующим образом:

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: В настоящее время, использование: https://github.com/PromiseKit/Alamofire-

+7

Можете ли вы привести пример использования? возможно, выполнение запросов, отправленных в вопрос? –

16

У вас есть несколько вариантов.


Вариант 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.

+0

Вариант 3 может быть дополнен ответом @ mxcl – jlhonora

+0

Вы абсолютно правы ... Я соответствующим образом обновил ответ. – cnoon

+0

Ваши первые два варианта включают вложение, которое является тем, чего обещают избежать. Поэтому я не уверен, что имеет смысл сказать, что Alamofire справляется с этим довольно хорошо. Разве вы не говорите, что вложенность не проблема? –

16

я написал класс, который обрабатывает цепочку запроса по одному.

Я создал класс 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 
     } 

    } 
} 

Пример использования Свифта 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 

     } 
+0

Это очень круто и исправлено проблема, которую я испытывал очень элегантно. Теперь он жалуется, когда работает в Swift 3 request.response (completeHandler: {(_, _, _, error), давая ошибку «Невозможно вызвать значение non-function type HTTPURLResponse?». Спасибо. – iphaaw

+0

Эй, @iphaaw Я обновил сообщение с быстрым кодом 3.0. – Eike

+0

спасибо за обновление – iphaaw

5

Вот еще один способ сделать это (Swift 3, Alamofire 4.x) с использованием 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") 
      }) 

     }) 

    } 
} 
+0

Это выглядит очень элегантно, но как можно собирать данные в рамках 'уведомления? – Besi

+0

Просто объявите переменную, которая будет хранить данные перед вызовом запроса, заполнить ее каждым запросом и сделать что-то с переменной в вызове уведомления (она будет заполнена из данных запроса в это время). BTW, я буду обновлять код в ответе завтра (я нашел более надежный способ создания цепочек запросов) ... –

+0

В прошлом я использовал [PromiseKit] (http://promisekit.org), чтобы связать такие запросы. Я считаю, что это очень удобная структура, поэтому вы можете проверить ее. – Besi

-1

Назовите себя бесконечно и DEFINE END CONDITION. urlring для ссылки API и словарь для 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 
    } 
} 
Смежные вопросы