Swift: إغلاقات Swift: تعابير الإغلاق والإغلاقات الزائدة
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
- Closure expression syntax
- Trailing closure rules
- Shorthand parameter names
$0,$1, etc. - Value capture mechanism
- Custom sorting rules with closures
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:
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:
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:
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
// ============================================
// 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 📖 للعرض فقط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
// ============================================
// 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 📖 للعرض فقط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
// ============================================
// 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 📖 للعرض فقط["apple", "banana", "cherry", "date"]Common Pitfall: When using shorthand parameter names, you can omit
returnif the closure body contains only a single expression. If there are multiple statements, you must writereturn.
5. Value Capture
A closure can capture constants and variables from its surrounding context, even after that context no longer exists.
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
// ============================================
// 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 📖 للعرض فقط2 4 6Tip: 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
// ============================================
// 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 📖 للعرض فقطCheapest: Mouse Best value: Keyboard
6. Complete Example: User List Sorter
// ============================================
// 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 📖 للعرض فقط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
in keyword?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.$0 and $1 determined?$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.let sorter = { a, b in ... }; sorted(by: sorter).📖 Summary
- Closure expressions are wrapped in
{ }, with parameters and return type declared before theinkeyword - Trailing closures let you omit parentheses for the last argument, making calls more natural
- Shorthand parameter names
$0,$1,$2greatly simplify closure writing - Single-expression closures can omit the
returnkeyword (implicit return) - Closures can capture and modify variables and constants from their surrounding context
sorted(by:)is one of the most common closure use cases
📝 Exercises
- Basic: Given the integer array
[3, 7, 1, 9, 4, 6], writesorted(by:)three different ways (full closure, type inference, shorthand parameter names) to sort in descending order. - 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. - Challenge: Implement a
makeCalculatorfunction that takes an operator string (+,-,*,/) and returns the corresponding(Int, Int) -> Intclosure. Then use the function to create an addition closure and a multiplication closure, calculating each for(10, 5).