Returning data from async call in Swift function
Solution 1:
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)")
}
}
Solution 2:
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 aFuture
. 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 aFuture
. Getting to[Book]
is much better for the rest of your Swift code than handing around anNSArray
.
Solution 3:
The basic 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.