Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning data from async call in Swift function

I have created a utility class in my Swift project that handles all the REST requests and responses. I have built a simple REST API so I can test my code. I have created a class method that needs to return an NSArray but because the API call is async I need to return from the method inside the async call. The problem is the async returns void. If I were doing this in Node I would use JS promises but I can't figure out a solution that works in 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...     } } 
like image 469
Mark Tyers Avatar asked Aug 08 '14 12:08

Mark Tyers


2 Answers

You can pass callback, and call callback inside async call

something like:

class func getGenres(completionHandler: (genres: NSArray) -> ()) {     ...     let task = session.dataTaskWithURL(url) {         data, response, error in         ...         resultsArray = results         completionHandler(genres: resultsArray)     }     ...     task.resume() } 

and then call this method:

override func viewDidLoad() {     Bookshop.getGenres {         genres in         println("View Controller: \(genres)")          } } 
like image 195
Alexey Globchastyy Avatar answered Sep 21 '22 09:09

Alexey Globchastyy


Swiftz already offers Future, which is the basic building block of a Promise. A Future is a Promise that cannot fail (all terms here are based on the Scala interpretation, where a Promise is a Monad).

https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift

Hopefully will expand to a full Scala-style Promise eventually (I may write it myself at some point; I'm sure other PRs would be welcome; it's not that difficult with Future already in place).

In your particular case, I would probably create a Result<[Book]> (based on Alexandros Salazar's version of Result). Then your method signature would be:

class func fetchGenres() -> Future<Result<[Book]>> { 

Notes

  • I do not recommend prefixing functions with get in Swift. It will break certain kinds of interoperability with ObjC.
  • I recommend parsing all the way down to a Book object before returning your results as a Future. There are several ways this system can fail, and it's much more convenient if you check for all of those things before wrapping them up into a Future. Getting to [Book] is much better for the rest of your Swift code than handing around an NSArray.
like image 36
Rob Napier Avatar answered Sep 22 '22 09:09

Rob Napier