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:
- Maak een PUT-verzoek
- Een GET-verzoek indienen
- 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 RequestChain
gemaakt die Alamofire.Request
als 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
Antwoord 6
Je kunt de when
methode in PromiseKit
gebruiken om zoveel aanroepen toe te voegen als je wilt.
Hier is een voorbeeld van PromiseKit
docs:
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
}
}