Retour des données de L'appel async dans la fonction Swift
j'ai créé une classe utilitaire dans mon projet Swift qui gère toutes les autres requêtes et réponses. J'ai créé une API REST simple pour tester mon code. J'ai créé une méthode de classe qui doit retourner un NSArray mais comme L'appel API est async, je dois revenir de la méthode à l'intérieur de l'appel async. Le problème est que l'async renvoie le vide. Si je faisais cela dans Node j'utiliserais js promises mais je ne peux pas trouver une solution qui fonctionne dans Swift.
import Foundation
class Bookshop {
class func getGenres() -> NSArray {
println("Hello inside getGenres")
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
println(urlPath)
let url: NSURL = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
var resultsArray:NSArray!
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error) {
println(error.localizedDescription)
}
var err: NSError?
var options:NSJSONReadingOptions = NSJSONReadingOptions.MutableContainers
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: options, error: &err) as NSDictionary
if(err != nil) {
println("JSON Error (err!.localizedDescription)")
}
//NSLog("jsonResults %@", jsonResult)
let results: NSArray = jsonResult["genres"] as NSArray
NSLog("jsonResults %@", results)
resultsArray = results
return resultsArray // error [anyObject] is not a subType of 'Void'
})
task.resume()
//return "Hello World!"
// I want to return the NSArray...
}
}
6 réponses
, Vous pouvez passer de rappel et d'appel de rappel à l'intérieur d'appel asynchrone
quelque chose comme:
class func getGenres(completionHandler: (genres: NSArray) -> ()) {
...
let task = session.dataTaskWithURL(url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
et appelez cette méthode:
override func viewDidLoad() {
Bookshop.getGenres {
genres in
println("View Controller: \(genres)")
}
}
Swiftz offre déjà L'avenir, qui est la pierre angulaire d'une promesse. Un futur est une promesse qui ne peut pas échouer (tous les Termes ici sont basés sur L'interprétation Scala, où une promesse est un Monad ).
https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift
s'élargira avec un peu de chance à une promesse pleine de style Scala éventuellement (je pourrais l'écrire moi-même à un moment donné; je suis bien sûr, d'autres PRs seraient les bienvenus; ce n'est pas si difficile avec le futur déjà en place).
dans votre cas particulier, je créerais probablement un Result<[Book]>
(basé sur version D'Alexandros Salazar de Result
). Alors votre signature de méthode serait:
class func fetchGenres() -> Future<Result<[Book]>> {
Notes
- Je ne recommande pas de fonctions de préfixation avec
get
dans Swift. Il va briser certains types de interopérabilité avec ObjC. - je vous recommande de parcourir tout le chemin jusqu'à un objet
Book
avant de retourner vos résultats comme unFuture
. Il y a plusieurs façons dont ce système peut échouer, et c'est beaucoup plus pratique si vous vérifiez toutes ces choses avant de les emballer dans unFuture
. Pour le reste de votre code Swift, il est préférable de passer à[Book]
que de passer àNSArray
.
autre exemple:
class func getExchangeRate(#baseCurrency: String, foreignCurrency:String, completion: ((result:Double?) -> Void)!){
let baseURL = kAPIEndPoint
let query = String(baseCurrency)+"_"+String(foreignCurrency)
var finalExchangeRate = 0.0
if let url = NSURL(string: baseURL + query) {
NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
if ((data) != nil) {
let jsonDictionary:NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: nil) as NSDictionary
if let results = jsonDictionary["results"] as? NSDictionary{
if let queryResults = results[query] as? NSDictionary{
if let exchangeRate = queryResults["val"] as? Double{
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(result: exchangeRate)
}
}
}
}
}
}
else {
completion(result: nil)
}
}.resume()
}
}
appel:
Currency.getExchangeRate(baseCurrency: "USD", foreignCurrency: "EUR") { (result) -> Void in
if let exchangeValue = result {
print(exchangeValue)
}
}
version Swift 3 de la réponse de @Alexey Globchastyy:
class func getGenres(completionHandler: @escaping (genres: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
Swift 4.0
pour Async Request-Response vous pouvez utiliser completion handler. Voir ci-dessous j'ai modifié la solution avec completion handle paradigm.
func getGenres(_ completion: @escaping (NSArray) -> ()) {
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
print(urlPath)
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult["genres"] as! NSArray
print(results)
completion(results)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
vous pouvez appeler cette fonction comme suit:
getGenres { (array) in
// Do operation with array
}
self.urlSession.dataTask(with: request, completionHandler: { (data, response, error) in
self.endNetworkActivity()
var responseError: Error? = error
// handle http response status
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode > 299 , httpResponse.statusCode != 422 {
responseError = NSError.errorForHTTPStatus(httpResponse.statusCode)
}
}
var apiResponse: Response
if let _ = responseError {
apiResponse = Response(request, response as? HTTPURLResponse, responseError!)
self.logError(apiResponse.error!, request: request)
// Handle if access token is invalid
if let nsError: NSError = responseError as NSError? , nsError.code == 401 {
DispatchQueue.main.async {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
let message = apiResponse.message()
// Unautorized access
// User logout
return
}
}
else if let nsError: NSError = responseError as NSError? , nsError.code == 503 {
DispatchQueue.main.async {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
let message = apiResponse.message()
// Down time
// Server is currently down due to some maintenance
return
}
}
} else {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
self.logResponse(data!, forRequest: request)
}
self.removeRequestedURL(request.url!)
DispatchQueue.main.async(execute: { () -> Void in
completionHandler(apiResponse)
})
}).resume()