Step 1 of 5 · Reading · ~4 min
Learn
Functions and Closures
Closures
A closure is a function without a name, written inline where it is used. You have already seen
that a function has a type — (Int, Int) -> Int — and that a value of that type can be stored
and passed around. A closure is how you produce such a value on the spot.
The full form is braces containing a signature, the keyword in, and a body:
let addFull: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
return a + b
}
print(addFull(3, 4))
//> 7
Nobody writes that in practice. Swift lets you delete every part it can work out for itself, and the interesting thing is watching what remains.
Deleting things, one at a time
Take a list of parcel weights that we want sorted heaviest first. sorted(by:) takes a closure
of type (Int, Int) -> Bool that answers "should the first argument come before the second?"
let weights = [4, 12, 7]
let s1 = weights.sorted(by: { (a: Int, b: Int) -> Bool in return a > b })
let s2 = weights.sorted(by: { a, b in return a > b })
let s3 = weights.sorted(by: { a, b in a > b })
let s4 = weights.sorted(by: { $0 > $1 })
let s5 = weights.sorted(by: >)
let s6 = weights.sorted { $0 > $1 }
print(s1, s2, s3, s4, s5, s6)
//> [12, 7, 4] [12, 7, 4] [12, 7, 4] [12, 7, 4] [12, 7, 4] [12, 7, 4]
Six spellings, one result. What was removed at each step:
| Step | Removed | Because |
|---|---|---|
s2 | the types | sorted(by:) already declares them |
s3 | return | a single-expression closure returns it implicitly |
s4 | the parameter names | $0 and $1 are the positional stand-ins |
s5 | the closure entirely | > is already a function of the right type |
s6 | the parentheses | trailing-closure syntax |
Stop wherever the code stays readable. s4 and s6 are the everyday choices; s1 is what you
write when a closure grows past a line or two and named parameters start earning their keep.
$0, $1, $2
Inside a closure with no parameter list, the arguments are available as $0, $1 and so on. The
highest number you mention decides how many parameters the closure takes — use only $0 and it
is a one-parameter closure.
let double: (Int) -> Int = { $0 * 2 }
let sum: (Int, Int) -> Int = { $0 + $1 }
print(double(21), sum(20, 22))
//> 42 42
Trailing closures
When a closure is the last argument, it can move outside the parentheses. If it is the only argument, the parentheses disappear too.
✓ Both are the same call
let a = [1, 2, 3].map({ $0 * 10 })
let b = [1, 2, 3].map { $0 * 10 }
print(a, b)
//> [10, 20, 30] [10, 20, 30]
Almost every collection method in the standard library is shaped for this — map, filter,
sorted, reduce, forEach. It is why idiomatic Swift reads the way it does.
Closures capture their surroundings
A closure keeps hold of the variables it mentions, even after the scope that created them is gone. That is the "closing over" the name refers to:
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let next = makeCounter()
print(next(), next(), next())
//> 1 2 3
count is a local variable of makeCounter, which has already returned — yet it survives,
because the closure captured it. Two consequences worth knowing now:
- Closures are reference types. Assigning
nextto another name gives you a second handle on the same capturedcount, not a fresh one. - A closure stored on an object that captures
selfcan keep that object alive. That is the retain-cycle problem you will meet in real iOS code; the fix ([weak self]) is beyond this course, but knowing capture is real is the prerequisite.
Your exercise
Implement applyTwice(_ n: Int, _ op: (Int) -> Int) -> Int, which feeds n through op, then
feeds that result through op again.
The mistake the grader catches is applying the operation once. The starter body is
return 0, and the tempting one-line fix is return op(n) — with the caller's closure
{ $0 * $0 } that squares 2 to 4, where the first visible test wants 16. You need
op(op(n)): the output of the first call becomes the input of the second. Note also that the
starter's call site is applyTwice(n) { $0 * $0 } — trailing-closure syntax for the second
argument — so leave both underscores in the signature or that call stops compiling.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…