Swift: Swift Closures

Closures are self-contained blocks of functionality in Swift that can capture and store references to variables and constants from their surrounding context. This lesson starts with closure expressions and builds toward mastering one of Swift's most flexible language features.

1. What You'll Learn


2. A Real-World Data Analyst Story

(1) Pain Point: Non-Reusable Sorting Logic and Verbose Code

Alice needs to sort user data — by name, by age, by registration date. Her initial approach is to write a separate function for each sort:

SWIFT
func sortByName(_ a: [String: Any], _ b: [String: Any]) -> Bool {
    return (a["name"] as! String) < (b["name"] as! String)
}
func sortByAge(_ a: [String: Any], _ b: [String: Any]) -> Bool {
    return (a["age"] as! Int) < (b["age"] as! Int)
}

Every time the sort rule changes, she has to write a new function, causing code bloat. Sorting logic is scattered across multiple functions — you can't specify it dynamically at the call site.

(2) The Closure Solution

Swift closures let you inline the sort rule directly at the sorted(by:) call site:

SWIFT
let sortedByAge = users.sorted { $0["age"] as! Int < $1["age"] as! Int }
let sortedByName = users.sorted { $0["name"] as! String < $1["name"] as! String }

The sorting logic shifts from "write a function → pass it" to "define it where you use it."

(3) Benefit: Inline Definition, Flexible Switching

Dimension Standalone Function Inline Closure
Lines of Code 3-5 lines per sort 1 line
Readability Need to find function definition Logic visible at call site
Flexibility All strategies must be pre-defined Defined on the fly
Context Access Cannot capture external variables Can capture contextual state

3. Closure Expressions

(1) Basic Syntax

A closure expression is a concise way to write a closure. The full syntax is:

100%
graph LR
    A[Closure Expression] --> B["{ (parameters) -> ReturnType in"]
    A --> C["    statements"]
    A --> D["}"]
    B --> E["{ (a: Int, b: Int) -> Bool in"]
    C --> F["    return a < b"]
    D --> G["}"]
Component Required Notes
{ } Yes Closures are wrapped in curly braces
Parameter list No Can be omitted using shorthand names
-> ReturnType No Can be inferred automatically
in Conditional Required when parameters or return type are declared

▶ Example: Three Ways to Write Closure Expressions

SWIFT
// ============================================
// Three ways to write closures for sorted(by:)
// ============================================
let numbers = [5, 2, 8, 1, 9]
// Approach 1: Full closure expression
let sorted1 = numbers.sorted(by: { (a: Int, b: Int) -> Bool in
    return a < b
})
// Approach 2: Type inference (omit parameter types and return type)
let sorted2 = numbers.sorted(by: { a, b in return a < b })
// Approach 3: Omit return (single-expression implicit return)
let sorted3 = numbers.sorted(by: { a, b in a < b })
print("sorted3: \(sorted3)")

Output:

TEXT 📖 Display only
sorted3: [1, 2, 5, 8, 9]

4. Trailing Closures and Shorthand Parameter Names

(1) Trailing Closures

When a closure is the last argument of a function, you can move it outside the parentheses:

Style Example
Standard sorted(by: { a, b in a < b })
Trailing sorted { a, b in a < b }
Multi-parameter trailing animate(duration: 1) { ... }

▶ Example: Simplifying Calls with Trailing Closures

SWIFT
// ============================================
// Trailing closure concise syntax
// ============================================
let scores = [78, 92, 85, 68, 99]
// Standard syntax
let ascending1 = scores.sorted(by: { a, b in a < b })
// Trailing closure syntax
let ascending2 = scores.sorted { a, b in a < b }
print("Ascending: \(ascending2)")
// Multiple closure parameters also supported
func performRequest(url: String, onSuccess: (String) -> Void, onError: (Error) -> Void) {
    // Simulate network request
    onSuccess("Data received")
}
performRequest(url: "https://api.example.com") { data in
    print("Success: \(data)")
} onError: { error in
    print("Error: \(error)")
}

Output:

TEXT 📖 Display only
Ascending: [68, 78, 85, 92, 99]
Success: Data received

(2) Shorthand Parameter Names

Swift automatically provides shorthand names for inline closure parameters: $0, $1, $2, ...

Full Syntax Shorthand
{ a, b in a < b } { $0 < $1 }
{ name in print(name) } { print($0) }
{ a, b, c in a + b + c } { $0 + $1 + $2 }

▶ Example: Shorthand Parameter Names in Action

SWIFT
// ============================================
// Step-by-step simplification with shorthand names
// ============================================
let words = ["banana", "apple", "cherry", "date"]
// Full syntax
let sortedA = words.sorted(by: { (a: String, b: String) -> Bool in
    return a < b
})
// Shorthand parameter names — most concise
let sortedB = words.sorted(by: { $0 < $1 })
// Even simpler — pass the operator function directly
let sortedC = words.sorted(by: <)
print(sortedC)

Output:

TEXT 📖 Display only
["apple", "banana", "cherry", "date"]

Common Pitfall: When using shorthand parameter names, you can omit return if the closure body contains only a single expression. If there are multiple statements, you must write return.


5. Value Capture

A closure can capture constants and variables from its surrounding context, even after that context no longer exists.

100%
graph TB
    A[Outer function makeIncrementer] --> B["Declare variable total = 0"]
    A --> C["Return closure { total += amount; return total }"]
    C --> D[Closure captures reference to total]
    D --> E[Even after makeIncrementer returns]
    E --> F["total remains alive inside the closure"]

▶ Example: Closure Capturing Context Variables

SWIFT
// ============================================
// Value capture — generating an accumulator
// ============================================
func makeIncrementer(step: Int) -> () -> Int {
    var total = 0
    let incrementer: () -> Int = {
        total += step  // Captures total and step
        return total
    }
    return incrementer
}
let incrementByTwo = makeIncrementer(step: 2)
print(incrementByTwo()) // 2
print(incrementByTwo()) // 4
print(incrementByTwo()) // 6

Output:

TEXT 📖 Display only
2
4
6

Tip: Value capture is one of the most powerful features of closures. Swift automatically manages the memory of captured variables — a closure holds a strong reference to captured variables until the closure itself is released.

▶ Example: Custom Sorting Rules

SWIFT
// ============================================
// Custom sorting rules with closures
// ============================================
struct Product {
    let name: String
    let price: Double
    let rating: Int
}
let products = [
    Product(name: "Laptop", price: 1299, rating: 5),
    Product(name: "Mouse", price: 29, rating: 4),
    Product(name: "Keyboard", price: 99, rating: 5),
    Product(name: "Monitor", price: 399, rating: 3)
]
// Sort by price ascending
let byPrice = products.sorted { $0.price < $1.price }
print("Cheapest: \(byPrice.first!.name)")
// Sort by rating descending, then by price ascending on tie
let byRatingThenPrice = products.sorted {
    if $0.rating != $1.rating {
        return $0.rating > $1.rating
    }
    return $0.price < $1.price
}
print("Best value: \(byRatingThenPrice.first!.name)")

Output:

TEXT 📖 Display only
Cheapest: Mouse
Best value: Keyboard

6. Complete Example: User List Sorter

SWIFT
// ============================================
// Complete example: User list sorter
// Features: Closure expressions + trailing closures + shorthand names + value capture
// ============================================
import Foundation
// 1. User model
struct User {
    let name: String
    let age: Int
    let score: Double
}
let users = [
    User(name: "Alice", age: 28, score: 92.5),
    User(name: "Bob", age: 22, score: 85.0),
    User(name: "Charlie", age: 35, score: 95.5),
    User(name: "Diana", age: 19, score: 78.0)
]
// 2. Sorter factory — captures sort direction
func makeSorter(ascending: Bool) -> (User, User) -> Bool {
    return { ascending ? $0.score < $1.score : $0.score > $1.score }
}
// 3. Sort using shorthand parameter names
let ascendingSorter = makeSorter(ascending: true)
let sortedAsc = users.sorted(by: ascendingSorter)
print("Ascending:")
for u in sortedAsc {
    print("  \(u.name): \(u.score)")
}
// 4. Inline sort with trailing closure
let sortedDesc = users.sorted { $0.score > $1.score }
print("Descending:")
for u in sortedDesc {
    print("  \(u.name): \(u.score)")
}

Output:

TEXT 📖 Display only
Ascending:
  Diana: 78.0
  Bob: 85.0
  Alice: 92.5
  Charlie: 95.5
Descending:
  Charlie: 95.5
  Alice: 92.5
  Bob: 85.0
  Diana: 78.0

❓ FAQ

Q What's the difference between a closure and a function?
A Closures are anonymous functions with a more concise syntax. Functions have names and can be called repeatedly; closures are typically used for one-off scenarios. Closures can capture contextual variables — nested functions can too, but regular global functions cannot.
Q When must I write the in keyword?
A When a closure expression declares a parameter list or return type, you must use in to separate the declaration from the body. If you use shorthand parameter names (like $0) and omit all type annotations, in is not needed.
Q How is the order of $0 and $1 determined?
A $0 is the first parameter of the closure, $1 is the second, and so on. The order is determined by the order in which arguments are passed when the closure is called, not by parameter names.
Q Are captured values copies or references?
A Closures capture variables by reference (similar to inout) — when multiple closures share the same variable, changes in one affect the other. Constants are captured by value copy.
Q What if a closure is too long and hurts readability?
A If the closure body exceeds 5-10 lines, extract it into a standalone function or a named closure variable before passing it in. For example: let sorter = { a, b in ... }; sorted(by: sorter).

📖 Summary


📝 Exercises

  1. Basic: Given the integer array [3, 7, 1, 9, 4, 6], write sorted(by:) three different ways (full closure, type inference, shorthand parameter names) to sort in descending order.
  2. Intermediate: Write a function repeatTask(times:task:) that takes a count and a closure, executing the closure the specified number of times. Then call it to print "Hello, Swift!" 5 times.
  3. Challenge: Implement a makeCalculator function that takes an operator string (+, -, *, /) and returns the corresponding (Int, Int) -> Int closure. Then use the function to create an addition closure and a multiplication closure, calculating each for (10, 5).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏