Swift: The Problem with Trailing Closure Syntax

UPDATE: See this discussion on Twitter on how to best handle the need for two closures for the success / failure blocks scenario.

Let’s say you have a class with a method that takes in a closure as an argument:

class MyFunClass {
    
    func doSomething(activity: String,
        handler: (String) -> Void)
    {
        // do something
    }
    
}

When you call this method, you can do it the old regular way (autocompleted like this by default):

let myFunClass = MyFunClass()

myFunClass.doSomething("fun", handler: { (funString) -> Void in
    // do something
})

Or you can use the Swift trailing closure syntax the new and cool way:

let myFunClass = MyFunClass()

myFunClass.doSomething("fun") { (funString) -> Void in
    // do something
}

The problem comes up when a method has two closures as arguments. For example, it is a common pattern to have a success and failure handlers when networking is involved:

class MyFunClass {
    
    func doSomething(activity: String,
        successHandler: ([String]) -> Void,
        failureHandler: (NSError) -> Void)
    {
        // do something
    }    
}

Unfortunately, when two closures are involved, Swift autocompletes to this (the second closure becomes trailing automatically):

let myFunClass = MyFunClass()

myFunClass.doSomething("fun",
    successHandler: { (result) -> Void in
    // execute
}) { (error) -> Void in
    // execute
}

Of course I can now modify this manually to this:

myFunClass.doSomething("fun",
    successHandler: { (result) -> Void in
    // execute
    }, failureHandler: { (error) -> Void in
    // execute
})

But I personally find it super annoying to have to manually modify how I call my methods with two completion handlers, and it doesn’t look too much more readable to me. The method ends up just being too long!

Instead, I’ve opted out of having two closures, and instead just use one, in which case I do like using the trailing closure syntax:

class MyFunClass {
      
    func doSomething(activity: String,
        completionHandler: (result: [String]?, error: NSError?) -> Void)
    {
        // do something
    }  
}

let myFunClass = MyFunClass()

myFunClass.doSomething("fun") { (result, error) in
    // do something
}

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • Konstantin Koval

    +1 to that approach. I liked that approach (having separate callback) in objc (you don’t need if statement than). But in Swift it doesn’t work so well

    It would be even better to have a result type of Failable (enum or tuple), so you don’t forget to handle error

  • Guest

    You can look at approach with Result

  • Tatiana Kornilova

    You can look at approach with Result https://gist.github.com/BestKora/381962c82e6459d1e450

  • Davide De Franceschi

    If the problem is to use trailing closures with multiple closure parameters, you can… more or less. I’ll put a simple example there:

    myFunClass.doSomethingCurried(“fun”) { (result) -> Void in //execute } () { (error) -> Void in //execute }

  • Omar Palomino Sandoval

    +1 nice approach