Skip to content

Closures

Closures are blocks of code that can be passed around like variables. They are similar to lambdas or anonymous functions in other languages. Closures can capture variables from their surrounding code, and Swift manages the memory for you.

Global functions and nested functions are special types of closures:

  • Global functions have names but don't capture values.
  • Nested functions have names and can capture values from their parent function.
  • Closure expressions are unnamed and can capture values from context.

Swift makes closures concise with features like:

  • Type inference for parameters and returns.
  • Implicit returns for single-line closures.
  • Shorthand arguments like $0, $1.
  • Trailing closure syntax.

Closure Expressions

Closure expressions let you write short, inline functions. They're useful for methods that take functions as arguments, like sorting arrays.

Swift's Array has a sorted(by:) method that sorts based on a closure you provide. It returns a new sorted array without changing the original.

Example array:

swift
let fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]

To sort in reverse alphabetical order, the closure should return true if the first string is greater than the second.

A full function version:

swift
func reverseSort(_ str1: String, _ str2: String) -> Bool {
    return str1 > str2
}
var sortedFruits = fruits.sorted(by: reverseSort)
// sortedFruits is ["Elderberry", "Date", "Cherry", "Banana", "Apple"]

Basic Syntax

General form:

swift
{ (parameters) -> returnType in
    statements
}

Inline example:

swift
sortedFruits = fruits.sorted(by: { (str1: String, str2: String) -> Bool in
    return str1 > str2
})

Shorter on one line:

swift
sortedFruits = fruits.sorted(by: { (str1: String, str2: String) -> Bool in return str1 > str2 })

Type Inference

Swift infers types from context, so omit types and ->:

swift
sortedFruits = fruits.sorted(by: { str1, str2 in return str1 > str2 })

Implicit Return

For single-expression closures, omit return:

swift
sortedFruits = fruits.sorted(by: { str1, str2 in str1 > str2 })

Shorthand Arguments

Use $0, $1 for arguments, omit in:

swift
sortedFruits = fruits.sorted(by: { $0 > $1 })

Operator as Closure

Use the > operator directly:

swift
sortedFruits = fruits.sorted(by: >)

Trailing Closures

If the last argument is a closure and it's long, write it after the parentheses.

Example function:

swift
func runTask(action: () -> Void) {
    // task code
}

Call with trailing closure:

swift
runTask {
    // action code
}

For sorting:

swift
sortedFruits = fruits.sorted() { $0 > $1 }

If it's the only argument, omit parentheses:

swift
sortedFruits = fruits.sorted { $0 > $1 }

Example with map: Convert numbers to spelled-out strings.

Dictionary:

swift
let numWords = [
    0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let values = [23, 45, 67]

Map with trailing closure:

swift
let spelled = values.map { (val) -> String in
    var val = val
    var result = ""
    repeat {
        result = numWords[val % 10]! + result
        val /= 10
    } while val > 0
    return result
}
// spelled is ["TwoThree", "FourFive", "SixSeven"]

For multiple closures, label after the first:

swift
func fetchData(from source: Source, success: (Data) -> Void, failure: () -> Void) {
    // code
}

fetchData(from: someSource) { data in
    // handle success
} failure: {
    // handle failure
}

Capturing Values

Closures capture variables from their context, even after the context ends.

Example: A function that returns a closure to add a fixed amount.

swift
func createAdder(addBy amount: Int) -> () -> Int {
    var total = 0
    func adder() -> Int {
        total += amount
        return total
    }
    return adder
}

let addFive = createAdder(addBy: 5)
addFive() // 5
addFive() // 10

let addThree = createAdder(addBy: 3)
addThree() // 3

Each closure has its own captured total.

Closures as Reference Types

Closures are reference types, so assigning to another variable shares the same instance.

swift
let alsoAddFive = addFive
alsoAddFive() // 15
addFive() // 20

Escaping Closures

Mark @escaping if the closure outlives the function, like storing it.

Example:

swift
var handlers: [() -> Void] = []
func addEscapingHandler(handler: @escaping () -> Void) {
    handlers.append(handler)
}

For classes, capture self explicitly to avoid cycles:

swift
class ExampleClass {
    var num = 0
    func run() {
        addEscapingHandler { self.num = 50 }
    }
}

Or use capture list:

swift
addEscapingHandler { [self] in num = 50 }

For structs, escaping closures can't mutate self.

Autoclosures

Autoclosures wrap expressions as closures automatically, delaying evaluation.

Example:

swift
var queue = ["Item1", "Item2", "Item3"]
let provider = { queue.remove(at: 0) } // Doesn't remove yet
provider() // "Item1"

Function version:

swift
func process(itemProvider: @autoclosure () -> String) {
    print("Processing \(itemProvider())")
}
process(item: queue.remove(at: 0)) // "Item2", removes now

For escaping: Use @autoclosure @escaping.

swift
var providers: [() -> String] = []
func collect(_ provider: @autoclosure @escaping () -> String) {
    providers.append(provider)
}
collect(queue.remove(at: 0))

Released under the MIT License.