Control Flow, Functions & Closures
iOS Development Fundamentals
Chapter 3 · Control Flow, Functions & Closures
This chapter covers the last piece of core Swift before SwiftUI itself starts in Chapter 5: branching and looping, writing real functions, and closures — a feature that looks like a small syntax detail here, but turns out to be exactly what SwiftUI's own view code leans on constantly.
if/else and switch
Swift's switch is genuinely more capable than the C-style version, and behaves differently in
one important real way: it doesn't require a break, and it does not fall
through to the next case by default.
case stops automatically once it finishes — a genuine, deliberate reversal
of C-family fall-through behavior. The rarely-needed fallthrough keyword exists specifically
for the rare case where continuing into the next case is actually wanted. A case can also
match several values at once, comma-separated: case 1, 2, 3:.
for-in and while Loops
Functions
A Swift function parameter can have two real, independent names: an external name (used
by the caller) and an internal name (used inside the function body). Writing just one name
makes it both — writing _ as the external name suppresses it entirely at the call site.
| Piece | What It Does |
|---|---|
_ name | External name suppressed — the caller writes greet("Sam"), not greet(name: "Sam"). |
formally isFormal | External name formally, internal name isFormal — the caller writes formally:, the body reads isFormal. |
= false | A real default parameter value — the argument can be omitted entirely at the call site. |
Closures
A closure is a real, self-contained block of functionality that can be passed around and used like any other value — assigned to a constant, passed as an argument, returned from a function.
When a closure is the last argument to a function, Swift allows a real, dedicated shorthand — trailing closure syntax — writing the closure outside the parentheses:
VStack { Text("Hi") } is a real function call to
VStack's own initializer, with a trailing closure describing its child views. Nothing about
SwiftUI's own layout code in Chapter 7 onward is new syntax — it's this same trailing-closure pattern,
applied to real view types.
Closures Capture Values from Their Surrounding Scope
makeCounter() creates its own genuinely independent count — the
returned closure keeps that specific variable alive for as long as the closure itself exists, even after
makeCounter has already returned. This same capturing behavior is what makes closures
genuinely powerful for SwiftUI, and it's also the real source of a subtle memory-management topic —
strong reference cycles — covered properly once Chapter 3 of Architecture & Data introduces classes.
Hands-On Exercises
Write a function categorize(score: Int) that uses a switch statement with real ranges to print "Fail" (0-49), "Pass" (50-69), or "Distinction" (70 and above). Test it with three real values, one from each range.
Write a function repeatAction(times: Int, action: () -> Void) that calls action the given number of times, then call it using trailing closure syntax to print "Tap!" three times.
Explain, in your own words, why the two calls to makeCounter() in this chapter's own example each produce a genuinely independent counter, rather than sharing one underlying count value.
Chapter 3 Quick Reference
- Swift's
switchneeds nobreakand doesn't fall through by default — usefallthroughfor the rare case that needs it, and ranges/comma-separated values in onecase - Function parameters can have separate external/internal names, plus real default values
- Closures are self-contained blocks of functionality that can be assigned, passed, and returned like any value
- Trailing closure syntax (
fn(args) { ... }) is exactly the shape SwiftUI's own view builders use —VStack { ... }is just a function call with a trailing closure - Closures capture variables from their surrounding scope, keeping them alive for as long as the closure itself exists