Control Flow
Swift provides control flow statements to structure code execution, including loops, conditional branches, and early exits. These tools allow you to iterate over sequences, make decisions based on conditions, and control the flow of execution. Key control flow mechanisms include for-in loops, while loops, if, guard, switch, defer, and control transfer statements like break, continue, and fallthrough.
Note
Swift’s control flow is type-safe and optimized for clarity and performance. Always ensure conditions and patterns are exhaustive to avoid runtime errors.
For-In Loops
The for-in loop iterates over sequences like arrays, dictionaries, ranges, or strings. It’s ideal for processing collections or performing tasks a set number of times.
Iterating Over an Array
let fruits = ["Apple", "Banana", "Orange"]
for fruit in fruits {
print("I like \(fruit)!")
}
// Prints:
// I like Apple!
// I like Banana!
// I like Orange!Iterating Over a Dictionary
Dictionaries return key-value pairs as tuples during iteration. You can decompose these tuples into named constants:
let pets = ["Dog": 4, "Bird": 2, "Fish": 0]
for (pet, legs) in pets {
print("\(pet)s have \(legs) legs.")
}
// Prints (order not guaranteed):
// Dogs have 4 legs.
// Birds have 2 legs.
// Fish have 0 legs.Note
Dictionary iteration order is not guaranteed. To iterate in a specific order, use sorted() on keys or values. For example:
for pet in pets.keys.sorted() {
print("\(pet): \(pets[pet]!) legs")
}
// Prints in alphabetical order:
// Bird: 2 legs
// Dog: 4 legs
// Fish: 0 legsIterating Over a Range
Use ranges for numeric iteration:
for num in 1...3 {
print("\(num) squared is \(num * num)")
}
// Prints:
// 1 squared is 1
// 2 squared is 4
// 3 squared is 9Ignoring Loop Values
Use an underscore (_) to ignore loop values when they’re not needed:
var total = 0
for _ in 1...5 {
total += 10
}
print("Total is \(total)")
// Prints: Total is 50Half-Open Ranges and Strides
Use ..< for half-open ranges (excluding the upper bound):
for tick in 0..<4 {
print("Tick \(tick)")
}
// Prints:
// Tick 0
// Tick 1
// Tick 2
// Tick 3Use stride(from:to:by:) for custom increments, excluding the upper bound:
for tick in stride(from: 0, to: 10, by: 2) {
print("Tick \(tick)")
}
// Prints:
// Tick 0
// Tick 2
// Tick 4
// Tick 6
// Tick 8Use stride(from:through:by:) to include the upper bound:
for tick in stride(from: 2, through: 8, by: 2) {
print("Tick \(tick)")
}
// Prints:
// Tick 2
// Tick 4
// Tick 6
// Tick 8Note
Any type conforming to the Sequence protocol can be used in a for-in loop. Custom collections can implement this protocol for iteration.
While Loops
While loops execute statements until a condition becomes false. They’re useful when the number of iterations is unknown. Swift offers two forms:
while: Evaluates the condition before each iteration.repeat-while: Executes at least once, evaluating the condition after each iteration.
While Loop
var count = 0
while count < 3 {
print("Count: \(count)")
count += 1
}
// Prints:
// Count: 0
// Count: 1
// Count: 2Repeat-While Loop
var steps = 0
repeat {
print("Step: \(steps)")
steps += 1
} while steps < 3
// Prints:
// Step: 0
// Step: 1
// Step: 2Example: Number Guessing Game
This example simulates a number guessing game where the player tries to guess a target number:
let target = 7
var guess = 1
while guess != target {
print("Guessing \(guess)...")
guess += 1
}
print("Correct! The number is \(guess).")
// Prints:
// Guessing 1...
// Guessing 2...
// Guessing 3...
// Guessing 4...
// Guessing 5...
// Guessing 6...
// Correct! The number is 7.Using repeat-while:
guess = 1
repeat {
print("Guessing \(guess)...")
guess += 1
} while guess != target
print("Correct! The number is \(guess).")
// Same output as aboveNote
repeat-while guarantees at least one iteration, making it suitable when initial execution is required before condition checking.
Conditional Statements
Swift provides if and switch statements for conditional execution. If is suited for simple conditions, while switch handles complex pattern matching.
If Statement
An if statement executes code based on a Boolean condition:
let score = 85
if score >= 90 {
print("Excellent!")
} else if score >= 70 {
print("Good job!")
} else {
print("Try again!")
}
// Prints: Good job!The else clause is optional:
if score >= 90 {
print("Excellent!")
}
// No output if score < 90If Expression
Swift supports if expressions for concise value assignments:
let grade = if score >= 90 {
"A"
} else if score >= 70 {
"B"
} else {
"C"
}
print("Grade: \(grade)")
// Prints: Grade: BFor optional values, specify the type explicitly:
let bonus: String? = if score >= 90 {
"Extra credit earned!"
} else {
nil as String?
}
print(bonus ?? "No bonus")
// Prints: No bonusNote
All branches in an if expression must return the same type to ensure type safety.
Switch Statement
A switch statement matches a value against patterns and executes the first matching case. It must be exhaustive, often using a default case.
let day = "Monday"
switch day {
case "Monday":
print("Start of the week")
case "Friday":
print("Weekend’s here!")
default:
print("Just another day")
}
// Prints: Start of the weekSwitch Expression
Like if, switch can be used as an expression:
let message = switch day {
case "Monday":
"Start of the week"
case "Friday":
"Weekend’s here!"
default:
"Just another day"
}
print(message)
// Prints: Start of the weekNo Implicit Fallthrough
Swift switch statements don’t fall through to the next case, preventing unintended execution:
let letter = "a"
switch letter {
case "a", "e":
print("Vowel")
case "b", "c":
print("Consonant")
default:
print("Other")
}
// Prints: VowelNote
Each case must have at least one executable statement. Empty cases cause compile-time errors unless using break.
Interval Matching
Match ranges of values:
let distance = 25
let rangeDescription = switch distance {
case 0:
"At the origin"
case 1..<10:
"Nearby"
case 10..<50:
"Moderate distance"
default:
"Far away"
}
print(rangeDescription)
// Prints: Moderate distanceTuple Matching
Match multiple values using tuples:
let point = (3, 0)
switch point {
case (0, 0):
print("At origin")
case (_, 0):
print("On x-axis")
case (0, _):
print("On y-axis")
case (1...5, 1...5):
print("In 5x5 square")
default:
print("Elsewhere")
}
// Prints: On x-axisValue Bindings
Bind values to constants within a case:
switch point {
case (let x, 0):
print("X-axis at \(x)")
case (0, let y):
print("Y-axis at \(y)")
default:
print("Elsewhere")
}
// Prints: X-axis at 3Where Clause
Add conditions to cases with where:
let coords = (2, 2)
switch coords {
case let (x, y) where x == y:
print("On line x = y")
case let (x, y) where x == -y:
print("On line x = -y")
default:
print("Elsewhere")
}
// Prints: On line x = yCompound Cases
Combine multiple patterns in a single case:
let char = "e"
switch char {
case "a", "e", "i":
print("Vowel")
case "b", "c", "d":
print("Consonant")
default:
print("Other")
}
// Prints: VowelCompound cases with value bindings require consistent types:
let position = (5, 0)
switch position {
case (let dist, 0), (0, let dist):
print("On axis, distance \(dist)")
default:
print("Not on axis")
}
// Prints: On axis, distance 5Patterns in If and For Loops
Patterns can be used in if and for-case-in constructs for concise matching.
If-Case
let testPoint = (10, 100)
if case (let x, 100) = testPoint {
print("Y=100 at x=\(x)")
}
// Prints: Y=100 at x=10For-Case-In
Filter collections with patterns:
let points = [(1, 0), (2, 2), (0, 3)]
for case (let x, 0) in points {
print("X-axis point at \(x)")
}
// Prints: X-axis point at 1Add a where clause for additional filtering:
for case let (x, y) in points where x == y {
print("Diagonal point at (\(x), \(y))")
}
// Prints: Diagonal point at (2, 2)Control Transfer Statements
Control transfer statements alter execution flow:
continue: Skips to the next loop iteration.break: Exits a loop orswitchimmediately.fallthrough: Continues to the nextswitchcase.return: Exits a function (see Functions).throw: Propagates errors (see Error Handling).
Continue
Skip the current iteration:
let text = "hello world"
var result = ""
let skip = ["e", "o", " "]
for char in text {
if skip.contains(char) {
continue
}
result.append(char)
}
print(result)
// Prints: hllwrldBreak in Loops
Exit a loop early:
var sum = 0
for num in 1...10 {
if sum > 15 {
break
}
sum += num
}
print("Sum: \(sum)")
// Prints: Sum: 21 (stops after 1+2+3+4+5+6)Break in Switch
Ignore a case with break:
let symbol: Character = "二"
var number: Int?
switch symbol {
case "1", "一":
number = 1
case "2", "二":
number = 2
default:
break
}
print(number ?? "No number")
// Prints: 2Fallthrough
Opt into C-style fallthrough behavior:
let value = 7
var desc = "Number \(value) is"
switch value {
case 3, 5, 7:
desc += " a small prime"
fallthrough
default:
desc += " and an integer"
}
print(desc)
// Prints: Number 7 is a small prime and an integerLabeled Statements
Labels clarify which loop or switch a break or continue targets in nested structures:
outerLoop: for i in 1...3 {
for j in 1...3 {
if j == 2 {
continue outerLoop
}
print("i=\(i), j=\(j)")
}
}
// Prints:
// i=1, j=1
// i=2, j=1
// i=3, j=1Example: Maze Navigation
Simulate navigating a maze where the player must reach position 10 exactly:
let target = 10
var position = 0
var move = 0
mazeLoop: while position != target {
move += 1
if move == 6 { move = 1 }
switch position + move {
case target:
position = target
break mazeLoop
case let newPos where newPos > target:
continue mazeLoop
default:
position += move
}
}
print("Reached position \(position)!")
// Prints: Reached position 10!Early Exit with Guard
A guard statement ensures a condition is true to continue execution, otherwise it executes an else block that must exit the scope:
func processUser(data: [String: String]) {
guard let username = data["username"] else {
print("No username provided")
return
}
print("Welcome, \(username)!")
}
processUser(data: ["username": "Alice"])
// Prints: Welcome, Alice!
processUser(data: [:])
// Prints: No username providedNote
guard improves readability by placing error-handling code near the condition and avoiding nested if blocks.
Deferred Actions
Use defer to execute code just before leaving a scope, regardless of how the scope is exited:
var balance = 100
if balance < 1000 {
defer { print("Final balance: \(balance)") }
balance += 50
}
print("Processing complete")
// Prints:
// Final balance: 150
// Processing completeMultiple defer blocks execute in reverse order:
if balance < 1000 {
defer { print("Balance: \(balance)") }
defer { print("Checking balance...") }
balance += 100
}
// Prints:
// Checking balance...
// Balance: 200Note
Deferred code runs after errors are thrown but not after crashes or runtime errors.
Checking API Availability
Use #available to check if APIs are available for a deployment target:
if #available(iOS 15, macOS 12, *) {
print("Using modern APIs")
} else {
print("Using legacy APIs")
}Use guard for availability in a scope:
func useFeature() -> String {
guard #available(iOS 15, *) else {
return "Feature unavailable"
}
return "Using iOS 15 feature"
}Use #unavailable for fallback code:
if #unavailable(iOS 15) {
print("Fallback for older iOS")
}Note
Specify platforms and versions explicitly, using * for others. Minor versions (e.g., iOS 15.2) are supported.