Understanding Swift Closure Syntax

Even though I encourage developers preparing for technical interviews to focus primarily on their approach to problem-solving, Closures are complex enough to require close study. In this challenge, our goal is to create a secondary function named operation() that will use our times() function as a parameter. Shown below, times is a simple function that returns the multiplication product of two input parameters:

 
//challenge: write a new function called operation() that applies the times() function as a parameter. 

  
func times(lhs: Int, rhs: Int)-> Int{
    return lhs * rhs
}

Being First Class

Successfully solving this challenge requires an understanding of Swift syntax. To start, we should acknowledge that specific objects such as enums, protocols and functions, often restricted in usage with other languages, act as first-class citizens in Swift. As such, Swift allows us to use them similar to how one would employ a standard Int or String type. Since functions can include parameters and return types, this makes expressing them as Closures somewhat detailed. Consider the following:

func times(lhs: Int, rhs: Int)-> Int {
    return lhs * rhs
}


func operation(formula: (Int, Int)-> Int) {
    let result = formula(2, 3) 
    print(result) //prints 6
}

//times applied as a parameter..
operation(formula: times)
 

Like this article? Get more content like this in the iOS Computer Science Lab.

 

We've been able to satisfy the requirements of the challenge by using the times() function as a parameter. One thing to note is the operation() function signature. The idea is that the parameter, formula, accepts two Int values and also returns an Int. When compared, this signature is also a match to the times function. As a result, the entire times() function can be passed as a parameter. 

 

Added Functionality

To see the benefits of our design, let's refactor the times function so that it adds two input values. Renamed to add(), consider the following:

func add(lhs: Int, rhs: Int)-> Int{
    return lhs + rhs
}


func operation(formula: (Int, Int)-> Int) {
    let result = formula(2, 3) 
    print(result) //prints 5
}

//add applied as a parameter..
operation(formula: add)

As shown, little refactoring was needed for formula() to produce a new result. In fact, no change occurred even though it now provides a new result. This a simple, but useful example of encapsulation - a design principle shared across many programming languages. However, to see how this relates specifically to Closure syntax, let's revise our example:

func operation(formula: (Int, Int)-> Int) {
    let result = formula(2, 3) 
    print(result) //prints 5
}

//inline trailing closure
operation { (lhs: Int, rhs: Int) -> Int in
    return lhs + rhs
}

As shown, we've combined the call to operation() with the add() function by applying a standard Swift Closure syntax. This technique not only makes our code more concise but also allows us to quickly change how formula() operates without changing its underlying structure.  

 

Nil Values

While not specifically asked in this example, a common question is how nil return types are handled with Closures. When we consider Optionals we know that Swift has specific ways to treat the absence of a value. When constructing trailing Closures, nil return types must be identified using empty parentheses or the Void type alias. For example:

func operation(formula: (Int, Int)-> ()) {
    formula(2, 3)
}

//inline trailing closure
operation { (lhs: Int, rhs: Int) -> Void in
    print("The result is : \(rhs + lhs)")
}