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...
    }
}
51
demandé sur Mark Tyers 2014-08-08 16:28:59

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)")     
    }
}
62
répondu Alexey Globchastyy 2014-08-08 12:59:40

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 un Future . 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 un Future . Pour le reste de votre code Swift, il est préférable de passer à [Book] que de passer à NSArray .
10
répondu Rob Napier 2017-05-23 11:47:17

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)
                }
            }
8
répondu ericgu 2015-03-10 13:57:59

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()
}
3
répondu Nebojsa Nadj 2017-05-16 18:55:32

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
}
2
répondu Jaydeep 2018-10-10 10:10:16
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()
0
répondu CrazyPro007 2018-05-19 16:42:14