4

I've had this problem a couple of times now, so thought I'd reach out.

I have a number of network interfaces responsible for making Async network calls, there's about 5/6 functions in each interface which all use a completion handler with the same definition:

(success: Bool, resources: [String: AnyObject] -> Void)

I'm looking for an alternative to adding this to the end of every function as it forces every function declaration onto 2/3 lines. e.g.

func performSomeNetworkCall(withThisParam parm1: AnyObject, param2: AnyObject, completion: (success: Bool, resources: [String: AnyObject] -> Void)) {

}

I've had a couple of ideas about how to solve the problem of long declarations and rewriting the completion definition for every function.

Thought One

Using a typealias to define the closure like so:

typealias CompletionHandler = (success: Bool, resources: [String: AnyObject] -> Void)?

This, in theory, solves both issues as it's quick to write and solves the code length problems. However when calling function from an external source, the typealias doesn't autocomplete like regular closures, meaning you have to write it out every-time leading to mistakes.


Thought Two

Using a code snippet (when Xcode actually remembers you set them). This in theory works as well, as long as every other developer working on the code also has the same code snippet and it aware of it instead of writing the entire declaration.


Thought Three

I thought about defining the completion handler as a function and passing the function as the parameter, something like this:

func completionHandler() -> (success: Bool, resources: [String: AnyObject] -> Void) {
        return completionHandler()
    }

But haven't been able to achieve what I wanted with that.



Edit

What I was hoping to achieve with Thought Three

func completionHandler() -> ((success: Bool, resources: [String: AnyObject]) -> Void) {
    return completionHandler()
}

func networkCall(completion: (success: Bool, resources: [String: AnyObject]) -> Void) {
    let request = NSURLRequest(URL: NSURL(string: "http://www.google.co.uk")!)

    let session = NSURLSession.sharedSession()

    session.dataTaskWithRequest(request) { (data, response, error) -> Void in
        let handler = completionHandler()
        handler(success: true, resources: [String: AnyObject]())
    }
}

func foo() {
    let handler = completionHandler()
    networkCall(handler)

    print(handler)
    // hope to use handler.success || handler.resources here
}

Although currently just gets stuck on the completionHandler method call - (To point out the obvious...)

Ollie
  • 1,926
  • 1
  • 20
  • 35
  • My first instinct would be thought three. Not sure why that doesn't work? What were you not able to achieve? Was it errors or it just didn't work the way you expected? – AtheistP3ace Feb 20 '16 at 15:58
  • @AtheistP3ace I've edited the question to expand on what I hoped to achieve with thought three. At the moment it just loops on the `completionHandler()` calls. I also don't get any suggestions from Xcode when trying to use the handler in `foo()`. e.g. It doesn't suggest `handler.success` – Ollie Feb 20 '16 at 18:46

2 Answers2

5

A typealias is the typical solution for this. It should autocomplete even outside of the original definition's file. I think the issue is in your definition of your typealias. If you define it like this (pay attention to the parentheses and no optional):

typealias CompletionHandler = (success: Bool, resources: [String: AnyObject]) -> Void

func performSomeNetworkCall(withThisParam parm1: AnyObject, param2: AnyObject, completion: CompletionHandler?) {
    // Implementation
}

Then autocompleting it works like so:

autocomplete for completion handler

After hitting the return key:

expanded autocomplete for completion handler

mclaughj
  • 12,645
  • 4
  • 31
  • 37
1

I'm looking for an alternative

IMHO, the better alternative to write much more concise code would utilise "Promises" or "Futures". These are not yet part of the Swift Standard Library, but other languages do have one or the other implementation - and there are already third party libraries for Swift.

For example, with "Scala-like" Futures your code may look as follows:

func performSomeNetworkCall(
    withThisParam parm1: AnyObject, 
    param2: AnyObject) 
    -> Future<[String: AnyObject]>

So, instead of a completion handler, the asynchronous function returns a Future. The future is a generic class, whose type parameter equals the computed value. A future can also represent an error, if the underlying asynchronous function fails.

You obtain the eventual result though registering a continuation via a combinator function:

let future = performSomeNetworkCall(param1, param2: param2)
future.map { computedValue in
    print("Result: \(computedValue)")
}
.onFailure { error in
    print("Error: \(error)")
}

Here, map is a combinator function with which we can register a continuation. A combinator returns a new future based on the return value of its continuation, and thus we can combine futures which perform more complex tasks.

The continuation function will be called when the future has been successfully completed. It get's passed the value of the underlying asynchronous function as its parameter. The continuation returns a value or Void.

Another combinator is flatMap. As opposed to map, it's continuation just returns another future:

login().flatMap { token in
    fetchUser(id: userId).map { user in 
        print("User: \(user)")
    }
}.onFailure { error in
    print("Error: \(error)")
}

The function login is an asynchronous function. When it completed successfully, it calls the asynchronous function fetchUser in its continuation. When fetchUser completes successfully, it prints the obtained user object.

If anything fails, the error will be propagated to the "failure handler" which has been registered with onFailure.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • This is a very interesting concept, and certainly true to "I'm looking for an alternative", I wasn't expecting anything like this so thanks! Can you recommend a third-party library? I saw a few different options on a Google search. – Ollie Feb 22 '16 at 13:51
  • 1
    @Ollie For Skala-like futures, there's Thomas Visser's [BrightFutures](https://github.com/Thomvis) - quite complete and high quality. Then there's Tuomas Kareinen's [MiniFuture](https://github.com/tkareine/MiniFuture), minimalistic but correct and also high quality code. My own attempt: [FutureLib](https://github.com/couchdeveloper/FutureLib). This is comparable to BrightFutures, but has somewhat more flexible cancellation and a bit more flexible API regarding throwing continuations and a more powerful execution context. There are other libraries, too - based on Objective-C. – CouchDeveloper Feb 22 '16 at 14:05