defer
In the evening, a post office clerk closes their counter however the shift went: they sorted all the parcels, or they sent the last one back with a stamp. So as not to forget, in the morning, right after opening the counter, they hang a note on it: "close when leaving". The note is read on the way out, whichever way the clerk leaves.
A function can also have several exits:
returnat the end;returnin aguardbranch;throw.
If the same thing must be done before each exit, sooner or later one of the exits gets forgotten. For this, Swift has defer:
enum ParseError: Error {
case badFormat
}
func parse(_ text: String) throws -> Int {
print("open", terminator: " ")
defer { print("close", terminator: " ") }
guard let n = Int(text) else {
throw ParseError.badFormat
}
return n
}
print((try? parse("3")) ?? 0) // open close 3
print((try? parse("x")) ?? 0) // open close 0Let's go through the program step by step:
print("open", terminator: " ")printsopen: the counter has opened.defer { … }hangs the note. The code in the braces does not run now: it is put off until the function exits.- In the call
parse("3"), the function exits throughreturn n. Right before the exit,deferfires and printsclose. Only then does the outerprintprint the result3. - In the call
parse("x"), the function exits throughthrow, anddeferfires all the same.try?turns the error intonil, and?? 0gives0.
When defer fires
The code in defer runs on leaving the block in braces where the defer is written. It does not matter how the block is left:
- through
return; - through
throw; - when the block's lines run out.
A block is not only a function body. A defer in a loop body fires at the end of every lap, and a defer in a do block fires on leaving do.
Several defer blocks
One block can hold several defer blocks. Then they fire in reverse order: the last one written runs first.
func shift() {
print("unlock", terminator: " ")
defer { print("lock") }
print("lamp on", terminator: " ")
defer { print("lamp off", terminator: " ") }
print("work", terminator: " ")
}
shift() // unlock lamp on work lamp off lockLet's walk through the function:
- The clerk unlocks the door and hangs the note "lock up".
- They turn on the lamp and hang a second note: "turn off the lamp".
- They work:
workis printed. - On the way out, the notes are read starting from the last one: first
lamp off, thenlock.
The reverse order is handy: what was opened last is closed first. The lamp goes off while the door is still unlocked.
Interviewers like to ask this: when does defer run, and in what order? The answer:
- on leaving the block where it is written: through
return, throughthrowor at the end of the block; - not at its own line, but after all the other lines of the block;
- several
deferblocks run in reverse order: the last one written runs first.
Here the note picture is off. A note on the counter is visible all day, and it can be carried out at any time. A defer cannot be carried out early: it fires only on leaving its block, and only if the program has reached its line.
Common mistake
defer was written below a line that can leave the function:
func late(_ text: String) throws -> Int {
print("open", terminator: " ")
guard let n = Int(text) else {
throw ParseError.badFormat
}
defer { print("close", terminator: " ") } // ✗
return n
}The program compiles, but for text that is not all digits, close is not printed. The function leaves through throw before it reaches the line with defer, so the note never gets hung. Write defer right after the thing it must close, before the first line that can leave the function.
Key points
deferputs code off until the block where it is written is left: throughreturn, throughthrowor at the end of the block.- The code in
deferruns after all the other lines of the block, not at its own line. - Several
deferblocks run in reverse order: the last one written runs first. deferfires only if execution has reached its line, so it is written before the first possible exit.