What's the difference between a defer statement and a statement right just before return?
Solution 1
What's the difference between a defer statement and a statement right just before return?
All the difference in the world. The defer
statement is executed after the return! This allows you to accomplish things that can be accomplished in no other way.
For example, you can return a value and then change the value. Apple makes use of this trick quite regularly; here, for example, is code from the Sequence documentation showing how to write a custom Sequence:
struct Countdown: Sequence, IteratorProtocol {
var count: Int
mutating func next() -> Int? {
if count == 0 {
return nil
} else {
defer { count -= 1 }
return count
}
}
}
If you wrote that as
count -= 1
return count
... it would break; we don't want to decrement count
and then return it, we want to return count
and then decrement it.
Also, as has been already pointed out, the defer
statement is executed no matter how you exit. And it works no matter you exit the current scope, which might not involve return
at all; defer
works for a function body, a while block, an if construct, a do block, and so on. A single return
is not the only way to exit such a scope! There might be more than one return
in your method, and/or you might throw an error, and/or you might have a break
, etc. etc., or you might just reach the last line of the scope naturally; the defer
is executed in every possible case. Writing the same code "by hand", so as to cover every possible exit, can be very error-prone.
Solution 2
In your example there is actually no difference, but please look at this:
func foo(url: URL) -> Int
let fileDescriptor : CInt = open(url.path, O_EVTONLY);
defer {
close(fileDescriptor)
}
guard let bar = something1() else { return 1 }
guard let baz = something2() else { return 2 }
doSomethingElse(bar, baz)
return 3
}
close(fileDescriptor)
is always executed regardless in which line the function returns.
Solution 3
defer statement is used to execute a piece of code exactly before the execution departs the recent scope.
For example:
func defer() {
print("Beginning")
var value: String?
defer {
if let v = value {
print("Ending execution of \(v)")
}
}
value = "defer function"
print("Ending")
}
First line which will print is: Beginning
Second line which will print is: Ending
And the last line which will print is: Ending execution of defer function.
Solution 4
Using defer
lets you avoid conditional clean-up at the end of the function.
Consider this example:
class Demo {
var a : String
init(_ a:String) {
self.a = a
}
func finish() {
print("Finishing \(a)")
}
}
func play(_ n:Int) {
let x = Demo("x")
defer { x.finish() }
if (n < 2) {return}
let y = Demo("y")
defer { y.finish() }
if (n < 3) {return}
let z = Demo("z")
defer { z.finish() }
}
play(1)
play(2)
play(3)
Function play
creates one, two, or three Demo
objects depending on its parameter, and calls finish
on them at the end of the run. If the function returns from the middle, defer
statements do not get executed, and finish
is not called for objects that never get created.
An alternative to this would require using optionals:
func play(_ n:Int) {
var x:Demo? = nil
var y:Demo? = nil
var z:Demo? = nil
x = Demo("x")
if (n >= 2) {
y = Demo("y")
}
if (n >= 3) {
z = Demo("z")
}
x?.finish()
y?.finish()
z?.finish()
}
This approach places all declarations at the top, and forces you to unwrap optionals later on. The code with defer
, on the other hand, lets you write clean-up code near the code that does initialization.
Related videos on Youtube
Li Fumin
Updated on July 16, 2022Comments
-
Li Fumin almost 2 years
What's the difference between this:
_ = navigationController?.popViewController(animated: true) defer { let rootVC = navigationController?.topViewController as? RootViewVC rootVC?.openLink(url: url) } return
and this:
_ = navigationController?.popViewController(animated: true) let rootVC = navigationController?.topViewController as? RootViewVC rootVC?.openLink(url: url) return
Apple's swift guideline says: “You use a defer statement to execute a set of statements just before code execution leaves the current block of code. ”,but still I don't quite get it.
-
Li Fumin over 7 yearsAre you sure defer statement is executed after the return? Swift programming language guide book says: "Use defer to write a block of code that is executed after all other code in the function, just before the function returns”。It says "before the function returns".
-
Rana over 6 yearsTried running in playground, defer statement was not called after return
-
Shubham about 6 yearsthe statements in the defer block are executed when the execution leaves the current scope. It has nothing to do with the before/after "return". What If the function has no return in it !
-
matt about 6 years@Shubham Yes, that’s what I said too, in the third paragraph. Defer is executed because we have left, no matter how. See apeth.com/swiftBook/ch05.html#_defer And did you follow the link and study this? stackoverflow.com/a/36185126/341994
-
marosoaie over 5 years@matt that's a nice example with incrementing the int. I guess that the int value is captured before executing the defer statement, and the defer statement is executed before the flow resumes in the calling code.
-
BangOperator about 5 years@matt To me it seems you are agreeing with Shubham where he says "It has nothing to do with the before/after return", there by contradicting your answer where you say "The defer statement is executed after the return"
-
matt about 5 years@BangOperator Read my comment again. He says "what if the function has no return in it?" I say that exit is exit no matter how; return is just one special case. But it is the special case that my answer deals with, because that's what the question was. — Also please see the link given in my answer; there is no need to deface my answer by adding an example that I already give.
-
boweidmann over 3 yearsnow I got the difference. The end of scope could be actually the end of an if statement. I thought scope was referred to the function scope. Thank you @matt
-
mskw over 2 yearsInstead of defer, just count -=1 first, then return (count+1).