Swift Programming – Closures

In this tutorial you’ll learn about closures in Swift. To follow along, it is recommended to have a Mac and the Xcode IDE installed on it. This tutorial makes use of the Xcode playground for compilation of the Swift codes. If you do not have a Mac, you may try using the open-source code editors which support different operating systems such as AtomSublimeText or VSCode.

Closures

A closure contains codes to perform a specific task, quite similar to a function. A closure can capture and store references of variables and constants defined in a context. Global and nested functions are known to be special cases of closures. There are 3 forms that closures can take:

  • Global functions – have a name and don’t capture any values.
  • Nested functions – have a name and can capture values from their enclosing function.
  • Closures – don’t have a name, are written in a lightweight syntax and can capture values from their surrounding context.

Closure Expressions

Closure expressions are inline closures written in a brief and focused syntax. Recall in the examples of Swift Programming – Collection Types, we used a Swift method sorted() to sort our Sets. Swift provides another sorted(by:) method where you provide a closure as its parameter.

let numbers = [105, 88, 217, 505, 3, 19, 1023]

func getDescendingNumbers(_ n1: Int, _ n2: Int) -> Bool {
    return n1 > n2
}

var descendingNumbers = numbers.sorted(by: getDescendingNumbers)

Closure Expression Syntax

The sorted(by:) method’s parameter in our example, takes a closure of two parameters of the type Int which returns a Bool and returns a sorted array. In this example, we have sorted the numbers in descending order. Notice that the function getDescendingNumbers(_:_:)‘s body is a single line of code. We can rewrite our code using the closure expression syntax so that the sorting closure becomes inline.

descendingNumbers = numbers.sorted(by: { (n1: Int, n2: Int) -> Bool in
    return n1 > n2
})

Inferring Type From Context

Swift can infer the types of its parameters and return type. Therefore, we can further simplify our code by omitting the parentheses around the names of the parameters and the return arrow (->).

descendingNumbers = numbers.sorted(by: { n1, n2 in return n1 > n2} )

Implicit Returns from Single-Expression Closures

Recall in our previous lesson on Swift Programming – Functions, we specified that we can omit the keyword (return) from our function if it’s a single line of code. We can therefore remove the return keyword from our code.

descendingNumbers = numbers.sorted(by: { n1, n2 in n1 > n2 })

Shorthand Argument Names

You can simplify further our code by using Swift’s shorthand argument names for inline closures whereby the arguments can be referred to $0 for the first value, $1 for the second value and so on. Therefore, the argument list and the in keyword can be omitted.

descendingNumbers = numbers.sorted(by: { $0 > $1 })

Operator Methods

There is a shorter way of writing our code. This implies using the operator method greater-than (>), and Swift can infer the rest.

descendingNumbers = numbers.sorted(by: >)

Trailing Closures

Trailing closures are useful when you want to pass a long closure expression as the final argument of a function. Let’s rewrite our previous example using a trailing closure.

descendingNumbers = numbers.sorted() { $0 > $1 }

If a function has only one argument and you are using a trailing closure, then you can omit the parentheses.

descendingNumbers = numbers.sorted { $0 > $1 }

Let’s use Swift’s map(_:) method to see the use of a trailing closure in action. Let’s say we have an array of String type and the items are all in lowercased. We want all the items to start with a capital letter.

let fruits = ["apple", "pineapple", "banana", "orange", "grape"]
let fruitsInCapital = fruits.map { fruit -> String in
    var isFirst = true
    var output = ""
    
    for character in fruit {
        var letter = String(character)
        
        if isFirst {
            letter = letter.uppercased()
            isFirst = false
        }
        
        output += String(letter)
    }
    
    return output
}

Capturing Values

A simple way of capturing values in a closure is by writing a nested function within the body of another.

func multiply(by product: Int) -> () -> (number: Int, output: Int) {
    var number = 0
    
    func multiplication() -> (number: Int, output: Int) {
        number += 1
        let output = number * product
        let result = (number, output)
        
        return result
    }
    
    return multiplication
}

let multiplyByFive = multiply(by: 5)
var multiplierFive = multiplyByFive()

print("\(multiplierFive.number) x 5 = \(multiplierFive.output)")
for _ in 2...12 {
    multiplierFive = multiplyByFive()
    print("\(multiplierFive.number) x 5 = \(multiplierFive.output)")
}

print()

let multiplyBySix = multiply(by: 6)
var multiplierSix = multiplyBySix()

print("\(multiplierSix.number) x 6 = \(multiplierSix.output)")
for _ in 2...12 {
    multiplierSix = multiplyBySix()
    print("\(multiplierSix.number) x 6 = \(multiplierSix.output)")
}

Closures Are Reference Types

Functions and closures are reference types, which is why despite we declared the closure as a constant, we are still able to keep multiplying the product with our captured value number. This also means if we assign a new constant or variable to the existing constant, it’ll continue from the last known value.

let multiplierSix2 = multiplyBySix()
print("\(multiplierSix2.number) x 6 = \(multiplierSix2.output)")

Reference

https://docs.swift.org/swift-book/LanguageGuide/Closures.html