Цепочка нескольких запросов Alamofire
Я ищу хороший шаблон, с помощью которого я могу связать несколько HTTP-запросов. Я хочу использовать Swift и предпочтительнее Alamofire.
Скажем, например, я хочу сделать следующее:
- Сделать запрос PUT
- Сделать запрос GET
- Обновить таблицу с данными
Похоже, что концепция 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 по сути асинхронен, так что шаблон не является вариантом.
Ответы
Ответ 1
Обтекание других асинхронных файлов в promises работает следующим образом:
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
У вас есть несколько вариантов.
Вариант 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
Я написал класс, который обрабатывает цепочку запросов один за другим.
Я создал класс 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
}
Ответ 4
Вот еще один способ сделать это (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")
})
})
}
}
Ответ 5
Называть себя бесконечно и DEFINE END CONDITION.
urlring для ссылки API и словарь для json
WE может создать модель очереди или делегировать
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
}
}