Skip to content
Lesson 9 of 15

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:

StepRemovedBecause
s2the typessorted(by:) already declares them
s3returna single-expression closure returns it implicitly
s4the parameter names$0 and $1 are the positional stand-ins
s5the closure entirely> is already a function of the right type
s6the parenthesestrailing-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 next to another name gives you a second handle on the same captured count, not a fresh one.
  • A closure stored on an object that captures self can 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.

Up nextArraysCollections

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…