Swift - Method chaining

2020-02-28 07:15发布

问题:

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?

回答1:

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



回答2:

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.