Categories
asynchronous ios rest swift

Returning data from async call in Swift function

105

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...
    }
}

1

19

Introduced in Swift 5.5 (iOS 15, macOS 12), we would now use the asyncawait pattern:

func fetchGenres() async throws -> [Genre] {
    …
    let (data, _) = try await URLSession.shared.dataTask(for: request)
    return try JSONDecoder().decode([Genre].self, from: data)
}

And we would call it like:

let genres = try await fetchGenres()

The asyncawait syntax is far more concise and natural than the traditional completion handler pattern outlined in my original answer, below.

For more information, see Meet async/await in Swift.


The historic pattern is to use completion handlers closure.

For example, we would often use Result:

func fetchGenres(completion: @escaping (Result<[Genre], Error>) -> Void) {
    ...
    URLSession.shared.dataTask(with: request) { data, _, error in 
        if let error = error {
            DispatchQueue.main.async {
                completion(.failure(error))
            }
            return
        }

        // parse response here

        let results = ...
        DispatchQueue.main.async {
            completion(.success(results))
        }
    }.resume()
}

And you’d call it like so:

fetchGenres { results in
    switch results {
    case .failure(let error):
        print(error.localizedDescription)

    case .success(let genres):
        // use `genres` here, e.g. update model and UI            
    }
}

// but don’t try to use `genres` here, as the above runs asynchronously

Note, above I’m dispatching the completion handler back to the main queue to simplify model and UI updates. Some developers take exception to this practice and either use whatever queue URLSession used or use their own queue (requiring the caller to manually synchronize the results themselves).

But that’s not material here. The key issue is the use of completion handler to specify the block of code to be run when the asynchronous request is done.


Note, above I retired the use of NSArray (we don’t use those bridged Objective-C types any more). I assume that we had a Genre type and we presumably used JSONDecoder, rather than JSONSerialization, to decode it. But this question didn’t have enough information about the underlying JSON to get into the details here, so I omitted that to avoid clouding the core issue, the use of closures as completion handlers.

2

  • You can use Result in Swift 4 and lower, too, but you have to declare the enum yourself. I’m using this kind of pattern for years.

    – vadian

    Feb 14, 2019 at 10:40


  • Yes, of course, as have I. But it only looks like it’s been embraced by Apple with the release of Swift 5. They’re just late to the party.

    – Rob

    Feb 14, 2019 at 16:50

14

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.

5

  • 4

    Swiftz no longer support Future. But take a look at github.com/mxcl/PromiseKit it works great with Swiftz!

    – badeleux

    Jul 28, 2015 at 8:45

  • 1

    took me a few seconds to realize you didn’t write Swift and wrote Swiftz

    – mfaani

    Aug 18, 2017 at 19:01

  • 5

    It sounds like “Swiftz” is a third party functional library for Swift. Since your answer seems to be based on that library, you should state that explicitly. (e.g. “There is a third party library called ‘Swiftz’ that supports functional constructs like Futures, and should serve as a good starting-point if you want to implement Promises.”) Otherwise your readers are just going to wonder why you misspelled “Swift”.

    – Duncan C

    Jan 2, 2018 at 21:55

  • 4

    Please note that github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift is not working anymore.

    – Ahmad F

    Mar 1, 2018 at 7:16

  • 1

    @Rob The get prefix indicates return-by-reference in ObjC (such as in -[UIColor getRed:green:blue:alpha:]). When I wrote this I was concerned that the importers would leverage that fact (to return a tuple automatically for example). It’s turned out that they haven’t. When I wrote this I probably had also forgotten that KVC supports “get” prefixes for accessors (it’s something I’ve learned and forgotten several times). So agreed; I haven’t run into any cases where the leading get breaks things. It’s just misleading to those who know the meaning of ObjC “get.”

    Feb 8, 2019 at 17:33