8

I'd like to implement method chaining in my swift code, likely to Alamofire methods. For example, if I have to use my function like below

getListForID(12).Success {
   // Success block
}. Failure {
   // Failure block
}

How would I create the function getListForID?

iOS
  • 3,526
  • 3
  • 37
  • 82
  • You can return an object that has `success` and `failure` property and the `success` setter also returns that, btw you can use promiseKit that does it http://promisekit.org/ – Daniel Krom May 09 '16 at 11:09
  • You can try my article that does just that: [Chaining Asynchronous Functions in Swift](https://medium.com/@jhoomuck/composing-asynchronous-functions-in-swift-acd24cf5b94a) – zeitgeist7 Aug 11 '17 at 18:31

2 Answers2

8

To expand on the great points @dasblinkenlight and @Sulthan have made – here's a small example of how you could achieve your request function to take a success and failure closure, in the convenient syntax that you want.

First, you'll have to define a new class to represent the 'result handler'. This is what your success and failure functions will pass around, allowing you to add multiple trailing closures to make up your completion block logic. You'll want it to look something like this:

class ResultHandler {

    typealias SuccessClosure = RequestHandler.Output->Void
    typealias FailureClosure = Void->Void

    // the success and failure callback arrays
    private var _successes = [SuccessClosure]()
    private var _failures = [FailureClosure]()

    /// Invoke all the stored callbacks with a given callback result
    func invokeCallbacks(result:RequestHandler.Result) {

        switch result {
            case .Success(let output): _successes.forEach{$0(output)}
            case .Failure: _failures.forEach{$0()}
        }
    }

    // remove all callbacks – could call this from within invokeCallbacks
    // depending on the re-usability of the class
    func removeAllCallbacks() {
        _successes.removeAll()
        _failures.removeAll()
    }

    /// appends a new success callback to the result handler's successes array
    func success(closure:SuccessClosure) -> Self {
        _successes.append(closure)
        return self
    }

    /// appends a new failure callback to the result handler's failures array
    func failure(closure:FailureClosure) -> Self {
        _failures.append(closure)
        return self
    }
}

This will allow you to define multiple success or failure closures to be executed on completion. If you don't actually need the capacity for multiple closures, then you can simplify the class down by stripping out the arrays – and just keeping track of the last added success and failure completion blocks instead.

Now all you have to do is define a function that generates a new ResultHandler instance and then does a given asynchronous request, with the invokeCallbacks method being invoked upon completion:

func doRequest(input:Input) -> ResultHandler {
    let resultHandler = ResultHandler()
    doSomethingAsynchronous(resultHandler.invokeCallbacks)
    return resultHandler
}

Now you can call it like this:

doRequest(input).success {result in
    print("success, with:", result)
}.failure {
    print("fail :(")
}

The only thing to note is your doSomethingAsynchronous function will have to dispatch its completion block back to the main thread, to ensure thread safety.


Full project (with added example on usage): https://github.com/hamishknight/Callback-Closure-Chaining

Hamish
  • 78,605
  • 19
  • 187
  • 280
6

In order to understand what is going on, it would help to rewrite your code without the "convenience" syntax, which lets you omit parentheses when a closure is the last parameter of a function:

getListForID(12)
    .Success( { /* Success block */ } )
    .Failure( { /* Failure block */ } )

This makes the structure of the code behind this API more clear:

  • The return value of getListForID must be an object
  • The object must have two function called Success and Failure*
  • Both Success and Failure need to take a single parameter of closure type
  • Both Success and Failure need to return self

* The object could have only Success function, and return a different object with a single Failure function, but then you wouldn't be able to re-order the Success and Failure handlers, or drop Success handler altogether.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Also, one possible implementation is a simple wrapper for two arrays - one array of success handlers and one array of failure handlers, when the `Success` and `Failure` methods just add the parameter to the given array. – Sulthan May 09 '16 at 11:23
  • @Sulthan Absolutely! I tried to explain the syntax, without going into much detail as to what would happen *inside* the `Success` and `Failure` methods. Both methods would take a closure as an argument; what to do with these closures is up to the method implementations, although storing closures in separate arrays for future use makes perfect sense. – Sergey Kalinichenko May 09 '16 at 11:48