Functions | Swift - Wyatt's Notes
Functions
Section titled “Functions”Functions are self-contained blocks of code that perform a specific task. Swift functions are first-class citizens: they can be assigned to variables, passed as arguments, and returned from other functions.
Basic Syntax
Section titled “Basic Syntax”func greet(name: String) -> String { return "Hello, \(name)!"}print(greet(name: "Alice")) // Hello, Alice!
// Functions without parametersfunc sayHello() { print("Hello, World!")}
// Functions without return valuefunc log(_ message: String) { print("[LOG] \(message)")}
// Implicit return for single-expression functionsfunc double(_ x: Int) -> Int { x * 2 }Parameters
Section titled “Parameters”// Argument label + parameter namefunc greet(person name: String) -> String { return "Hello, \(name)!"}greet(person: "Alice") // Argument label is "person'
// Omitting argument label with _func square(_ number: Int) -> Int { return number * number}square(5) // No argument label needed
// Default parameter valuesfunc power(_ base: Int, _ exponent: Int = 2) -> Int { var result = 1 for _ in 0..<exponent { result *= base } return result}power(3) // 9 (exponent defaults to 2)power(3, 3) // 27
// Variadic parametersfunc average(_ numbers: Double...) -> Double { guard !numbers.isEmpty else { return 0 } let sum = numbers.reduce(0, +) return sum / Double(numbers.count)}average(1, 2, 3, 4, 5) // 3.0
// Inout parameters -- modify the caller's variablefunc swapValues(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp}var x = 10, y = 20swapValues(&x, &y)print("x: \(x), y: \(y)") // x: 20, y: 10
// Multiple return values with tuplesfunc minMax(array: [Int]) -> (min: Int, max: Int)? { guard let first = array.first else { return nil } var currentMin = first var currentMax = first for value in array { if value < currentMin { currentMin = value } if value > currentMax { currentMax = value } } return (currentMin, currentMax)}
if let bounds = minMax(array: [3, 7, 1, 9, 4]) { print("Min: \(bounds.min), Max: \(bounds.max)")}Function Types
Section titled “Function Types”Every function has a type, composed of its parameter types and return type.
func add(_ a: Int, _ b: Int) -> Int { a + b }func multiply(_ a: Int, _ b: Int) -> Int { a * b }
// Function type: (Int, Int) -> Intvar operation: (Int, Int) -> Int = addprint(operation(3, 4)) // 7
operation = multiplyprint(operation(3, 4)) // 12
// Function type as parameterfunc apply(_ a: Int, _ b: Int, _ f: (Int, Int) -> Int) -> Int { return f(a, b)}apply(3, 4, add) // 7apply(3, 4, multiply) // 12apply(3, 4, { $0 - $1 }) // -1
// Function type as return typefunc stepped(increment: Bool) -> (Int) -> Int { if increment { return { $0 + 1 } } else { return { $0 - 1 } }}let stepUp = stepped(increment: true)print(stepUp(5)) // 6Nested Functions
Section titled “Nested Functions”func selectOperation(_ mode: String) -> (Double, Double) -> Double { func add(_ a: Double, _ b: Double) -> Double { a + b } func subtract(_ a: Double, _ b: Double) -> Double { a - b } func multiply(_ a: Double, _ b: Double) -> Double { a * b }
switch mode { case "add": return add case "subtract": return subtract case "multiply": return multiply default: return add }}
let calc = selectOperation("add")print(calc(3.0, 4.0)) // 7.0Closures
Section titled “Closures”Closures are self-contained blocks of code that can capture and store references to constants and variables from their surrounding context. Swift handles all memory management for captured variables automatically.
Closure Expression Syntax
Section titled “Closure Expression Syntax”// Full syntaxlet greetFull = { (name: String) -> String in return "Hello, \(name)!"}
// Inferring type from contextlet names = ["Alice", "Bob", "Carol"]let reversed = names.sorted(by: { (a: String, b: String) -> Bool in return a > b})
// Implicit returns from single-expression closureslet sorted = names.sorted(by: { a, b in a > b })
// Shorthand argument names ($0, $1, ...)let shortest = names.sorted(by: { $0.count < $1.count })
// Operator methods as closureslet alphabetical = names.sorted(by: <)Trailing Closure Syntax
Section titled “Trailing Closure Syntax”When the last argument of a function is a closure, write it after the function call using trailing closure syntax.
func transform(_ values: [Int], using closure: (Int) -> Int) -> [Int] { return values.map(closure)}
// Trailing closurelet doubled = transform([1, 2, 3]) { $0 * 2 } // [2, 4, 6]
// Multiple trailing closures (Swift 5.3+)func load(url: String, onSuccess: (Data) -> Void, onFailure: (Error) -> Void) { // ...}load(url: "https://example.com") { data in print("Success: \(data.count) bytes")} onFailure: { error in print("Failure: \(error)")}Capturing Values
Section titled “Capturing Values”func makeCounter() -> () -> Int { var count = 0 return { count += 1 return count }}
let counter = makeCounter()print(counter()) // 1print(counter()) // 2print(counter()) // 3// 'count' is captured and persists across calls
func makeIncrementer(increment amount: Int) -> () -> Int { var total = 0 return { total += amount return total }}
let incrementBy5 = makeIncrementer(increment: 5)print(incrementBy5()) // 5print(incrementBy5()) // 10print(incrementBy5()) // 15Capturing List
Section titled “Capturing List”Control how values are captured by using [unowned self] or [weak self].
class NetworkManager { var requestCount = 0
func fetchData(completion: @escaping () -> Void) { // Without capture list: strong reference to self DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.requestCount += 1 completion() }
// With capture list: weak reference (avoid retain cycle) DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in self?.requestCount += 1 completion() } }}Escaping Closures
Section titled “Escaping Closures”A closure is non-escaping by default, meaning it is executed before the function returns. An escaping closure is stored or executed after the function returns, requiring the @escaping annotation.
// Non-escaping (default)func perform(_ action: () -> Void) { action() // Executed synchronously before return}
// Escaping -- closure outlives the functionclass EventStore { var handlers: [() -> Void] = []
func subscribe(_ handler: @escaping () -> Void) { handlers.append(handler) // Stored for later execution }
func notify() { handlers.forEach { $0() } }}
// Escaping with async workfunc download(url: String, completion: @escaping (Result<Data, Error>) -> Void) { URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in if let data { completion(.success(data)) } else if let error { completion(.failure(error)) } }.resume()}@escaping and Sendable (Swift 5.6+)
Section titled “@escaping and Sendable (Swift 5.6+)”// Swift 6 requires Sendable for closures sent across concurrency boundariesfunc submitTask(_ work: @escaping @Sendable () -> Void) { Task.detached { work() }}Autoclosures
Section titled “Autoclosures”An @autoclosure wraps an expression in a closure, deferring evaluation until the closure is called.
// Evaluates condition lazilyfunc assert(_ condition: @autoclosure () -> Bool, _ message: String) { if !condition() { print("Assertion failed: \(message)") }}
var debugMode = falseassert(debugMode, "Debug mode should be on")// The expression `debugMode` is only evaluated inside assert()
// @autoclosure + @escapingfunc collect(operations: inout [@escaping () -> Void], _ op: @autoclosure @escaping () -> Void) { operations.append(op)}Higher-Order Functions
Section titled “Higher-Order Functions”Swift provides several higher-order functions on collections.
let prices = [10.0, 20.0, 30.0]let withTax = prices.map { $0 * 1.2 }// [12.0, 24.0, 36.0]
// Map with indexlet indexed = prices.enumerated().map { (index, price) in "\(index + 1). $\(price)"}// ["1. $10.0", "2. $20.0", "3. $30.0"]
// flatMap for flattening nested arrayslet nested = [[1, 2], [3], [4, 5, 6]]let flat = nested.flatMap { $0 }// [1, 2, 3, 4, 5, 6]
// flatMap for filtering nil from optionalslet inputs: [String?] = ["42", nil, "hello", "7", nil]let numbers = inputs.compactMap { $0.flatMap(Int.init) }// [42, 7]
// compactMap -- filter out nil, unwrap non-nillet possibleNumbers = ["1", "two", "3", "four", "5"]let validNumbers = possibleNumbers.compactMap { Int($0) }// [1, 3, 5]filter
Section titled “filter”let scores = [45, 82, 67, 91, 55, 78, 93, 60]let passing = scores.filter { $0 >= 60 }// [82, 67, 91, 78, 93, 60]
let highScorers = scores.filter { $0 >= 80 }.sorted(by: >)// [93, 91, 82]reduce
Section titled “reduce”let numbers = [1, 2, 3, 4, 5]let sum = numbers.reduce(0, +) // 15let product = numbers.reduce(1, *) // 120let joined = ["Hello", "World"].reduce("", { $0 + " " + $1 })// " Hello World"
// reduce(into:) for efficiencylet grouped: [Character: [Int]] = [1, 2, 3, 4, 5].reduce(into: [:]) { result, number in let key = number % 2 == 0 ? "e" : "o" result[key, default: []].append(number)}// ["o": [1, 3, 5], "e": [2, 4]]contains, first, allSatisfy
Section titled “contains, first, allSatisfy”let words = ["apple", "banana", "cherry", "avocado"]words.contains { $0.hasPrefix("a") } // truewords.first { $0.count > 5 } // "banana"words.allSatisfy { $0.count > 2 } // trueforEach
Section titled “forEach”let names = ["Alice", "Bob", "Carol"]names.forEach { print("Hello, \($0)") }
// Note: cannot use break or continue in forEach// Use for-in if you need control flowChaining Higher-Order Functions
Section titled “Chaining Higher-Order Functions”let students = [ (name: "Alice", score: 92), (name: "Bob", score: 78), (name: "Carol", score: 95), (name: "Dave", score: 82), (name: "Eve", score: 61)]
let honours = students .filter { $0.score >= 80 } .sorted { $0.score > $1.score } .map { "\($0.name): \($0.score)" }// ["Carol: 95", "Alice: 92", "Dave: 82"]Property Wrappers
Section titled “Property Wrappers”Property wrappers encapsulate get/set logic in a reusable wrapper type. SwiftUI relies heavily on property wrappers.
Creating a Custom Property Wrapper
Section titled “Creating a Custom Property Wrapper”@propertyWrapperstruct Capitalized { private var value: String = ""
var wrappedValue: String { get { value } set { value = newValue.capitalized } }
init(wrappedValue: String) { self.wrappedValue = wrappedValue }}
struct UserProfile { @Capitalized var firstName: String @Capitalized var lastName: String}
var profile = UserProfile(firstName: "alice", lastName: "smith")print(profile.firstName) // Aliceprint(profile.lastName) // SmithProjected Value
Section titled “Projected Value”@propertyWrapperstruct Clamped<Value: Comparable> { var wrappedValue: Value { didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) } }
let range: ClosedRange<Value>
var projectedValue: ClosedRange<Value> { range }
init(wrappedValue: Value, range: ClosedRange<Value>) { self.range = range self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }}
struct GameSettings { @Clamped(range: 0...100) var volume: Int = 50}
var settings = GameSettings()print(settings.volume) // 50settings.volume = 150print(settings.volume) // 100 (clamped)print(settings.$volume) // 0...100 (projected value)SwiftUI Property Wrappers
Section titled “SwiftUI Property Wrappers”import SwiftUI
struct CounterView: View { // @State: Local state, value type, view-owned @State private var count = 0
// @Binding: Two-way binding to a parent's state // Used in child views
// @ObservedObject: Reference type conforming to ObservableObject @ObservedObject var viewModel = GameViewModel()
// @StateObject: Owns and creates the ObservableObject @StateObject var manager = DataManager()
// @EnvironmentObject: Injected from parent hierarchy @EnvironmentObject var appSettings: AppSettings
// @Environment: Read system/environment values @Environment(\.colorScheme) var colorScheme @Environment(\.dismiss) var dismiss
// @FetchRequest: Core Data query // @ScaledMetric: Dynamic type scaling // @FocusState: Keyboard focus management
var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } } }}
// Binding examplestruct ParentView: View { @State private var isOn = false
var body: some View { ToggleView(isOn: $isOn) // Pass binding }}
struct ToggleView: View { @Binding var isOn: Bool
var body: some View { Toggle("Feature", isOn: $isOn) }}Key Paths
Section titled “Key Paths”Key paths provide type-safe references to properties.
struct Person { let name: String var age: Int}
let nameKeyPath = \Person.namelet ageKeyPath = \Person.age
let alice = Person(name: "Alice", age: 30)print(alice[keyPath: nameKeyPath]) // Alice
// Key paths with arrayslet people = [ Person(name: "Alice", age: 30), Person(name: "Bob", age: 25), Person(name: "Carol", age: 35)]
let names = people.map(\.name) // ["Alice", "Bob", "Carol"]let sorted = people.sorted(by: \.age) // [Bob(25), Alice(30), Carol(35)]
// Key paths in sorting and filteringlet adults = people.filter { $0[keyPath: \.age] >= 30 }// [Alice(30), Carol(35)]Enumerations with Associated Values
Section titled “Enumerations with Associated Values”Basic Enums
Section titled “Basic Enums”enum CompassDirection { case north, south, east, west}
var direction = CompassDirection.northdirection = .south // Shorthand when type is known
switch direction {case .north: print("Heading north")case .south: print("Heading south")case .east: print("Heading east")case .west: print("Heading west")}Enums with Associated Values
Section titled “Enums with Associated Values”enum NetworkResponse { case success(data: Data, statusCode: Int) case failure(error: Error) case redirect(to: URL)}
func handle(response: NetworkResponse) { switch response { case .success(let data, let code): print("Success (\(code)): \(data.count) bytes") case .failure(let error): print("Error: \(error.localizedDescription)") case .redirect(let url): print("Redirect to: \(url)") }}Enums with Raw Values
Section titled “Enums with Raw Values”enum Planet: Int { case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune}
let earthOrder = Planet.earth.rawValue // 3
enum HTTPMethod: String { case get = "GET" case post = "POST" case put = "PUT" case delete = "DELETE"}
let method = HTTPMethod.post.rawValue // "POST"
// Initialising from raw valueif let planet = Planet(rawValue: 3) { print(planet) // earth}Enums as Function Types
Section titled “Enums as Function Types”enum Operation { static func add(_ a: Double, _ b: Double) -> Double { a + b } static func subtract(_ a: Double, _ b: Double) -> Double { a - b } static func multiply(_ a: Double, _ b: Double) -> Double { a * b } static func divide(_ a: Double, _ b: Double) -> Double { a / b }}
let compute: (Double, Double) -> Double = Operation.addprint(compute(3.0, 4.0)) // 7.0Call Operators and Subscripts
Section titled “Call Operators and Subscripts”struct Matrix { let rows: Int, cols: Int var grid: [Double]
init(rows: Int, cols: Int) { self.rows = rows self.cols = cols self.grid = Array(repeating: 0, count: rows * cols) }
subscript(row: Int, col: Int) -> Double { get { grid[row * cols + col] } set { grid[row * cols + col] = newValue } }}
var m = Matrix(rows: 3, cols: 3)m[0, 1] = 5.0print(m[0, 1]) // 5.0Intuition
Section titled “Intuition”Closures in Swift are like captured moments. A closure remembers the variables from the context where it was created, even after that context is gone. This is like a photograph that preserves not just the subject but the entire scene. The closure carries that scene with it wherever it goes.
Trailing closure syntax is like putting the most important instruction last. When the last argument to a function is a closure, Swift lets you write it outside the parentheses. This makes the code read more logically, like saying I want to do this, and here is how.
Worked Examples
Section titled “Worked Examples”Example 1: Curried Function for Configuration
Section titled “Example 1: Curried Function for Configuration”Problem: Create a curried function that builds a URL request from a base URL, path, and headers. Each step should be independently reusable.
func makeRequest(base: String) -> (String) -> ([(String, String)]) -> URLRequest { return { path in return { headers in let url = URL(string: base + path)! var request = URLRequest(url: url) for (key, value) in headers { request.addValue(value, forHTTPHeaderField: key) } return request } }}
let apiRequest = makeRequest(base: "https://api.example.com")let usersRequest = apiRequest("/users")([("Authorization", "Bearer token123")])print(usersRequest.url?.absoluteString ?? "")// https://api.example.com/usersExplanation: Currying transforms a multi-parameter function into a chain of single-parameter functions. Each intermediate function can be stored and reused independently. This pattern is useful for building configurable factories where some parameters are fixed early and others are provided later.
Example 2: Closure-Based Retry with Exponential Backoff
Section titled “Example 2: Closure-Based Retry with Exponential Backoff”Problem: Write a higher-order function that retries an async operation with exponential backoff, accepting a custom retry handler via a closure parameter.
func withRetry<T>( maxAttempts: Int, initialDelay: TimeInterval, operation: @escaping () async throws -> T, onRetry: ((Int, Error) -> Void)? = nil) async throws -> T { var lastError: Error? var delay = initialDelay
for attempt in 1...maxAttempts { do { return try await operation() } catch { lastError = error onRetry?(attempt, error) if attempt < maxAttempts { try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) delay *= 2 } } } throw lastError!}
// Usagelet result = try await withRetry(maxAttempts: 3, initialDelay: 1.0, operation: { try await fetchUserProfile(id: 42)}, onRetry: { attempt, error in print("Attempt \(attempt) failed: \(error)")})Explanation: The function accepts operation as an @escaping closure because it is called asynchronously across Task.sleep boundaries. The optional onRetry closure provides a hook for logging or metrics without affecting retry logic. Exponential backoff doubles the delay after each failure.
Example 3: Type-Safe Event Emitter with Closures
Section titled “Example 3: Type-Safe Event Emitter with Closures”Problem: Implement an event emitter that uses closures as handlers, supporting typed events and allowing handlers to be registered and removed.
class EventEmitter<EventType: Hashable> { private var handlers: [EventType: [String: (Any) -> Void]] = [:] private var counter = 0
func on<T>(_ event: EventType, handler: @escaping (T) -> Void) -> String { let id = "handler_\(counter)" counter += 1 handlers[event, default: :][id] = { value in guard let typed = value as? T else { return } handler(typed) } return id }
func off(_ event: EventType, id: String) { handlers[event]?.removeValue(forKey: id) }
func emit<T>(_ event: EventType, _ value: T) { handlers[event]?.values.forEach { $0(value) } }}
// Usageenum AppEvent { case login, logout, settingsChanged}
let emitter = EventEmitter<String>()let loginId = emitter.on("login") { (name: String) in print("Welcome, \(name)!")}emitter.on("login") { (name: String) in print("Audit: \(name) logged in")}
emitter.emit("login", "Alice")// Welcome, Alice!// Audit: Alice logged in
emitter.off("login", id: loginId)emitter.emit("login", "Bob")// Audit: Bob logged inExplanation: The generic EventType parameter allows the emitter to be keyed by any hashable type (strings, enums, etc.). Each handler is stored with a unique string ID for later removal. The internal storage uses (Any) -> Void with type casting, providing type safety at the public API boundary while maintaining flexibility internally.
flowchart TD
A[1_Functions] --> B[Key Concepts]
A --> C[Core Principles]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
C --> F[Design patterns]
D --> G[Real-world usage]Summary
Section titled “Summary”Swift functions are first-class values that can be stored, passed, and returned. Closures provide inline function definitions with shorthand syntax. Escaping closures handle asynchronous work, while property wrappers encapsulate storage logic. Higher-order functions (map, filter, reduce) enable concise, expressive data transformations.
Common Mistakes
Section titled “Common Mistakes”Forgetting that closures capture variables by reference. Closures in Swift capture variables by reference, not by value. If a closure modifies a captured variable, the change persists after the closure executes. This can cause unexpected side effects. Use capture lists ([x]) to capture values explicitly when needed.
Confusing trailing closure syntax with regular function calls. When the last argument to a function is a closure, you can use trailing closure syntax: func { body } instead of func({ body }). However, this only works for the last closure parameter. If there are multiple closure parameters, only the last one can use trailing syntax.
Not distinguishing between escaping and non-escaping closures. By default, closure parameters are non-escaping, meaning they execute before the function returns. Mark closures as @escaping when they are stored or executed after the function returns (e.g., in async callbacks). Forgetting @escaping causes compilation errors for stored closures.
Cross-References
Section titled “Cross-References”- Variables and Types - How closures capture variables and how optionals affect function signatures
- Error Handling - How throwing functions extend the function type system
- Classes and Structs - How methods, initializers, and deinitializers structure object behavior