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:
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:
func send(_ message: String, to recipient: String) {
print("Sending \(message) to \(recipient)")
}
send("Hello", to: "Alice")
Example: Variadic Parameters:
func sum(_ numbers: Int...) -> Int {
return numbers.reduce(0, +)
}
print(sum(1, 2, 3, 4)) // 10
Example: In-Out Parameters:
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:
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:
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:
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:
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:
let conciseSorted = numbers.sorted { $0 < $1 }
let doubled = numbers.map { $0 * 2 } // [10, 4, 16, 2]
Trailing Closure Example:
let filtered = numbers.filter { number in
number > 5
}
print(filtered) // [8]
Capturing Values
Closures capture variables from their environment, retaining them until execution.
Example:
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:
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:
func perform(_ closure: () -> Void) {
closure()
}
perform { print("Immediate execution") }
Autoclosures
Delay evaluation of expressions passed as arguments.
Example:
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:
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:
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
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