Какая разница между оператором defer и выражением прямо перед возвратом?

Какая разница между этим:

_ = navigationController?.popViewController(animated: true)

defer {
    let rootVC = navigationController?.topViewController as? RootViewVC
    rootVC?.openLink(url: url)
}
return

и это:

_ = navigationController?.popViewController(animated: true)

let rootVC = navigationController?.topViewController as? RootViewVC
rootVC?.openLink(url: url)
return

Apple swift guideline говорит: "Вы используете оператор defer для выполнения набора инструкций непосредственно перед тем, как выполнение кода покидает текущий блок кода". Но все же я не совсем понял.

Ответ 1

Какая разница между отложенным утверждением и утверждением непосредственно перед возвратом?

Вся разница в мире. Оператор defer выполняется после возврата! Это позволяет вам выполнять вещи, которые не могут быть выполнены никаким другим способом.

Например, вы можете вернуть значение, а затем изменить его. Apple использует этот трюк довольно регулярно; здесь, например, код из документации последовательности, показывающий, как написать собственную последовательность:

struct Countdown: Sequence, IteratorProtocol {
    var count: Int

    mutating func next() -> Int? {
        if count == 0 {
            return nil
        } else {
            defer { count -= 1 }
            return count
        }
    }
}

Если вы написали это как

            count -= 1
            return count

... это сломалось бы; мы не хотим уменьшать count и затем возвращать его, мы хотим вернуть count и затем уменьшить его.

Кроме того, как уже указывалось, оператор defer выполняется независимо от того, как вы выходите. И это работает независимо от того, выходите ли вы из текущей области видимости, что может вообще не включать return; defer работает для тела функции, блока while, конструкции if, блока do и так далее. Единственный return - не единственный способ выйти из такой сферы! В вашем методе может быть несколько return, и/или вы можете выдать ошибку, и/или у вас может быть break, и т.д. И т.д., Или вы можете просто достичь последней строки области видимости; defer выполняется в каждом возможном случае. Написание одного и того же кода "вручную", чтобы охватить все возможные выходы, может быть очень подвержено ошибкам.

Ответ 2

В вашем примере на самом деле нет разницы, но, пожалуйста, посмотрите на это:

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) всегда выполняется независимо от того, в какой строке возвращается функция.

Ответ 3

инструкция defer используется для выполнения части кода точно до того, как выполнение отходит от последней области.

Например:

func defer()  { 
 print("Beginning") 
 var value: String? 
 defer { 
    if let v = value { 
        print("Ending execution of \(v)")
    } 
 } 
 value = "defer function" 
 print("Ending")
}

Первая строка, которая будет напечатана: Начало

Вторая строка, которая будет печататься: Ending

И последняя строка, которая будет напечатана: Завершение выполнения функции отсрочки.

Ответ 4

Использование defer позволяет избежать условной очистки в конце функции.

Рассмотрим следующий пример:

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)

Функция play создает один, два или три объекта Demo в зависимости от его параметра и вызывает finish на них в конце прогона. Если функция возвращается из середины, операторы defer не выполняются, а finish не вызывается для объектов, которые никогда не создаются.

Альтернативой этому может потребоваться использование опций:

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()
}

Этот подход помещает все объявления вверху и заставляет вас разворачивать дополнительные опции позже. Код с defer, с другой стороны, позволяет писать код очистки рядом с кодом, который выполняет инициализацию.

Ответ 5

Guard - Guard проверяет некоторое условие и, если оно оценивается как ложное, выполняется оператор else, который обычно существует как метод.

Defer - Defer будет ожидать выполнения блока кода, пока не выйдет текущая область (цикл, метод и т.д.).

Ответ 6

Я хотел бы подчеркнуть, что "оператор defer выполняется после возврата"

class Test {
    var value: Int = 0 {
        didSet {
            print("Value is now \(value)")
        }
    }
    func incrementor() -> Int {
        defer {
            print("In defer")
            value = value + 1
        }
        print("Just before return")
        return value
    }
}

let test = Test()
test.value = 1
print("Will call incrementor")
let result = test.incrementor()
print("Did increment with result \(result)")

Консольный выход

Value is now 1
Will call incrementor
Just before return
In defer
Value is now 2
Did increment with result 1

Отсрочка вызывается после возврата, следовательно, старое значение 1 было возвращено вместо увеличенного значения 2.