'What's the difference between a defer statement and a statement right just before return?

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.



Solution 1:[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:[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:[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:[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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 vadian
Solution 3 H S Progr
Solution 4 Sergey Kalinichenko