Skip to content

Functions and Closures

Functions and closures are core to Swift’s modularity and expressiveness. Functions are named, reusable code blocks, while closures are anonymous functions that capture their environment. This document covers their syntax, features, and advanced usage.

Functions

Functions are declared with func, specifying parameters, return types, and optional argument labels.

Basic Function Definition

Example:

swift
func greet(person: String, greeting: String = "Hello") -> String {
    return "\(greeting), \(person)!"
}

print(greet(person: "Alice")) // "Hello, Alice!"
print(greet(person: "Bob", greeting: "Hi")) // "Hi, Bob!"

Parameter Features

  • Argument Labels: External names for clarity, omitted with _.
  • Default Values: Optional parameters with defaults.
  • Variadic Parameters: Accept multiple arguments as an array.
  • In-Out Parameters: Modify external variables.

Example: Argument Labels:

swift
func send(_ message: String, to recipient: String) {
    print("Sending \(message) to \(recipient)")
}
send("Hello", to: "Alice")

Example: Variadic Parameters:

swift
func sum(_ numbers: Int...) -> Int {
    return numbers.reduce(0, +)
}
print(sum(1, 2, 3, 4)) // 10

Example: In-Out Parameters:

swift
func swapValues(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

var x = 5, y = 10
swapValues(&x, &y)
print(x, y) // 10, 5

Throwing Functions

Mark functions with throws for error handling.

Example:

swift
enum ValidationError: Error {
    case invalidInput
}

func validate(_ input: String) throws -> Bool {
    guard !input.isEmpty else {
        throw ValidationError.invalidInput
    }
    return true
}

do {
    try validate("")
} catch {
    print(error) // invalidInput
}

Nested Functions

Define functions within other functions for encapsulation.

Example:

swift
func processData(_ data: [Int]) -> [Int] {
    func double(_ n: Int) -> Int { n * 2 }
    return data.map(double)
}

print(processData([1, 2, 3])) // [2, 4, 6]

Function Types

Functions are first-class types, assignable to variables.

Example:

swift
let add: (Int, Int) -> Int = { $0 + $1 }
print(add(2, 3)) // 5

Closures

Closures are anonymous functions, often used as arguments or stored for later execution.

Basic Closure Syntax

Example:

swift
let numbers = [5, 2, 8, 1]
let sorted = numbers.sorted { (a: Int, b: Int) -> Bool in
    return a < b
}
print(sorted) // [1, 2, 5, 8]

Shorthand Closure Syntax

Swift simplifies closures with inferred types, trailing closures, and shorthand arguments ($0, $1).

Example:

swift
let conciseSorted = numbers.sorted { $0 < $1 }
let doubled = numbers.map { $0 * 2 } // [10, 4, 16, 2]

Trailing Closure Example:

swift
let filtered = numbers.filter { number in
    number > 5
}
print(filtered) // [8]

Capturing Values

Closures capture variables from their environment, retaining them until execution.

Example:

swift
func makeCounter() -> () -> Int {
    var count = 0
    return {
        count += 1
        return count
    }
}

let counter1 = makeCounter()
let counter2 = makeCounter()
print(counter1()) // 1
print(counter1()) // 2
print(counter2()) // 1

Escaping Closures

Closures marked @escaping can outlive the function’s scope, e.g., stored for later use.

Example:

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

addHandler { print("Handler executed") }
handlers[0]() // "Handler executed"

Non-Escaping Closures

Default closures are non-escaping, executed within the function’s lifetime.

Example:

swift
func perform(_ closure: () -> Void) {
    closure()
}
perform { print("Immediate execution") }

Autoclosures

Delay evaluation of expressions passed as arguments.

Example:

swift
func log(@autoclosure _ message: () -> String) {
    print(message())
}

log("Computed message") // Only computed when logged

Closure Capture Lists

Prevent retain cycles with [weak self] or [unowned self].

Example:

swift
class ViewController {
    var name = "View"
    lazy var handler: () -> Void = { [weak self] in
        print(self?.name ?? "Nil")
    }
}

let vc = ViewController()
vc.handler() // "View"

Function Composition

Combine functions for modular logic.

Example:

swift
func compose<A, B, C>(_ f: @escaping (B) -> C, _ g: @escaping (A) -> B) -> (A) -> C {
    return { x in f(g(x)) }
}

let square = { (x: Int) -> Int in x * x }
let double = { (x: Int) -> Int in x * 2 }
let doubleThenSquare = compose(square, double)
print(doubleThenSquare(3)) // (3 * 2)^2 = 36

Best Practices

  • Clear Naming: Use descriptive function and parameter names.
  • Trailing Closures: Enhance readability for complex closures.
  • Capture Lists: Use [weak self] to avoid retain cycles.
  • Escaping Closures: Mark explicitly when stored.
  • Autoclosures: Use for deferred evaluation (e.g., logging).
  • Type Aliases: Simplify complex function types.
  • Avoid Overloading: Keep function signatures distinct.

Troubleshooting

  • Retain Cycles: Check for strong references in closures.
  • Type Errors: Verify closure parameter and return types.
  • Escaping Issues: Ensure @escaping for stored closures.
  • Performance: Minimize closure captures in tight loops.
  • Ambiguous Closures: Add type annotations for clarity.

Example: Comprehensive Function and Closure Usage

swift
enum OperationError: Error {
    case invalidInput
}

class Calculator {
    typealias Operation = (Int, Int) -> Int
    private var history: [(String, Int)] = []
    private var completionHandlers: [() -> Void] = []
    
    func perform(_ operation: Operation, a: Int, b: Int, name: String, completion: @escaping () -> Void) throws -> Int {
        guard a >= 0, b >= 0 else {
            throw OperationError.invalidInput
        }
        let result = operation(a, b)
        history.append((name, result))
        completionHandlers.append(completion)
        return result
    }
    
    func executePendingCompletions() {
        completionHandlers.forEach { $0() }
        completionHandlers.removeAll()
    }
    
    func add(_ a: Int, _ b: Int) -> Int {
        func internalAdd(_ x: Int, _ y: Int) -> Int { x + y }
        return internalAdd(a, b)
    }
}

let calc = Calculator()
do {
    let sum = try calc.perform(+, a: 5, b: 3, name: "Addition") { print("Completed addition") }
    print("Sum: \(sum)") // 8
    let product = try calc.perform(*, a: 4, b: 2, name: "Multiplication") { print("Completed multiplication") }
    print("Product: \(product)") // 8
    calc.executePendingCompletions()
} catch {
    print(error)
}

let closureAdd = calc.add(10, 20)
print("Closure add: \(closureAdd)") // 30

Released under the MIT License.