Closures

In Swift, functions are considered first-class citizens, which means that they can be treated the same as any other type. They can be assigned to variables and be passed in and out of other functions. When treated this way, we call them closures. This is an extremely critical piece to write more declarative code because it allows us to treat functionalities like objects. Instead of thinking of functions as a collection of code to be executed, we can start to think about them more like a recipe to get something done. Just like you can give just about any recipe to a chef to cook, you can create types and methods that take a closure to perform some customizable behavior.

Closures as variables

Let's take a look at how closures work in Swift. The simplest way to capture a closure in a variable is to define the function and then use its name to assign it to a variable:

func double(input: Int) -> Int {
        return input * 2
}

var doubleClosure = double
print(doubleClosure(2)) // 4

As you can see, doubleClosure can be used just like the normal function name after being assigned. There is actually no difference between using double and doubleClosure. Note that we can now think of this closure as an object that will double anything passed to it.

If you look at the type of doubleClosure by holding the option key and click on the name, you will see that the type is defined as (Int) -> Int. The basic type of any closure is (ParamterType1, ParameterType2, …) -> ReturnType.

Using this syntax, we can also define our closure inline, such as:

var doubleClosure2 = { (input: Double) -> Double in
    return input * 2
}

We begin and end any closure with curly brackets ({}). Then, we follow the opening curly bracket with the type for the closure, which will include input parameters and a return value. Finally, we separate the type definition from the actual implementation with the in keyword.

An absence of a return type is defined as Void or (). Even though you may see that some programmers use parentheses, Void is preferred for return declarations:

var printDouble = { (input: Double) -> Void in
    print(input * 2)
}

Essentially, () is an empty tuple meaning it holds no value and it is more commonly used for the input parameters, in case the closure doesn't take any parameters at all:

var makeHelloWorld = { () -> String in
    return "Hello World!"
}

So far, even though we can change our thinking about the block of code by making it into a closure, it is not terribly useful. To really make closures useful, we need to start passing them into other functions.

Closures as parameters

We can define a function to take a closure as a parameter, using the same type syntax we saw previously:

func firstInNumbers(
    numbers: [Int],
    passingTest: (number: Int) -> Bool
    ) -> Int?
{
    for number in numbers {
        if passingTest(number: number) {
            return number
        }
    }
    return nil
}

Here, we have a function that can find the first number in an array that passes some arbitrary test. The syntax at the end of the function declaration may be confusing but it should be clear if you work from the inside out. The type for passingTest is (number: Int) -> Bool. That is then the second parameter of the whole firstInNumbers function, which returns an Int?. If we want to use this function to find the first number greater than three, we can create a custom test and pass that into the function:

let numbers = [1,2,3,4,5]
func greaterThanThree(number: Int) -> Bool {
    return number > 3
}
var firstNumber = firstInNumbers(numbers, greaterThanThree)
print(firstNumber) // "Optional(4)"

Here, we are essentially passing a little bundle of functionality to the firstInNumbers: function that lets us drastically enhance what a single function can normally do. This is an incredibly useful technique. Looping through an array to find an element can be very verbose. Instead, we can use this function to find an element showing only the important part of the code: the test.

We can even define our test right in a call to the function:

firstNumber = firstInNumbers(numbers, passingTest: { (number: Int) -> Bool in
    return number > 3
})

Even though this is more concise, it's pretty complex; hence, Swift allows us to cut out some of the unnecessary syntax.

Syntactic sugar

First, we can make use of type inference for the type of number. The compiler knows that number needs to be Int based on the definition of firstInNumbers:passingTest:. It also knows that the closure has to return Bool. Now, we can rewrite our call, as shown:

firstNumber = firstInNumbers(numbers, passingTest: { (number) in
    return number > 3
})

This looks cleaner, but the parentheses around number are not required; we could leave those out. In addition, if we have closure as the last parameter of a function, we can provide the closure outside the parentheses for the function call:

firstNumber = firstInNumbers(numbers) { number in
    return number > 3
}

Note that the closing parenthesis for the function parameters moved from being after the closure to before it. This is looking pretty great, but we can go even further. For a single line closure, we don't even have to write the return keyword because it is implied:

firstNumber = firstInNumbers(numbers) { number in
    number > 3
}

Lastly, we don't always need to give a name to the parameters of closures. If you leave out the names altogether, each parameter can be referenced using the syntax $<ParemterIndex>. Just like with arrays, the index starts at 0. This helps us write this call very concisely in a single line:

firstNumber = firstInNumbers(numbers) { $0 > 3 }

This is a long way from our original syntax. You can mix and match all of these different techniques to make sure that your code is as understandable as possible. As we have discussed before, understandability is a balance between being concise and clear. It is up to you in each circumstance to decide how much syntax you want to cut out. To me, it is not immediately clear what the closure is without it having a name. My preferred syntax for this is to use the parameter name in the call:

firstNumber = firstInNumbers(numbers, passingTest: {$0 > 3})

This makes it clear that the closure is a test to see which number we want to pull out of the list.

Now that we know what a closure is and how to use one, we can discuss some of the core features of Swift that allow us to write a functional style code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset