Swift 2: Ключевое слово defer

Swift 2: defer

Вот как оно было: неупорядоченные возвраты

Следующий код имитирует открытие файла, записи информации в файл и закрытие файла:

func writeLog() {
    let file = openFile()

    let hardwareStatus = fetchHardwareStatus()
    guard hardwareStatus != "катастрофа" else { return }
    file.write(hardwareStatus)

    let softwareStatus = fetchSoftwareStatus()
    guard softwareStatus != "катастрофа" else { return }
    file.write(softwareStatus)

    let networkStatus = fetchNetworkStatus()
    guard neworkStatus != "катастрофа" else { return }
    file.write(networkStatus)

    closeFile(file)
}

Мы не будет вникать в каждый метод, потому что по их названиям и так понятно что они делают. Вы могли заметить что файл открыт, затем в него вписываются некоторые данные, а после, файл закрывается.

Но что случится, если одна из проверок статуса вернет "катастрофу"? Ответ: наш оператор guard поймает ошибку и выйдет из метода (оставив файл открытым!).

Есть два варианта решения, но ни один из них не является классным. Первый вариант скопировать функцию closeFile(), чтобы она вызывалась перед каждым return. Второй вариант: создать "пирамиду погибели" с несколькими операторами условий для обработки записи в файл.

В Swift 2 мы можем решить эту проблему при помощи простого ключевого слова defer.

Отсроченная работа в Swift 2: defer

Swift 2 представляет нам новое ключевое слово defer, которое означает работу, которую мы хотим сделать при любых обстоятельствах. Это может быть абсолютно любая работа: метод, закрывающий файл или 100 строк кода, которые нужны для чистки. Очень важно: Swift гарантирует, что эта работа будет выполнена до того, как закончится ее область видимости.

Таким образом мы можем переписать наш код так:

func writeLog() {
    let file = openFile()
    defer { closeFile(file) }

    let hardwareStatus = fetchHardwareStatus()
    guard hardwareStatus != "катастрофа" else { return }
    file.write(hardwareStatus)

    let softwareStatus = fetchSoftwareStatus()
    guard softwareStatus != "катастрофа" else { return }
    file.write(softwareStatus)

    let networkStatus = fetchNetworkStatus()
    guard neworkStatus != "катастрофа" else { return }
    file.write(networkStatus)
}

При таком расположении defer функция closeFile() будет выполнена, несмотря на то, какой из операторов guard сработает, более того, функция сработает даже, если ни один из guard не сработает.

Это именно то, что мы и имеем в виду, когда говорим что отложенная работа всегда будет выполнена, независимо от того, где произойдет выход из метода. Будет это начало или середина - отложенная работа всегда будет выполнена.

defer является идеальным для выполнения функций чистки кода при любых условиях, что делает его похожим на try/finally в других языках.

Область видимости defer

Использование defer внутри метода означает, что отложенная работа будет выполнена перед выходом из метода. Например:

override func viewDidLoad() {
    super.viewDidLoad()

    print("Шаг 1")
    myFunc()
    print("Шаг 5")
}

func myFunc() {
    print("Шаг 2")
    defer { print("Шаг 3") }
    print("Шаг 4")
}

Этот код напечатает "Шаг 1", "Шаг 2", "Шаг 4", "Шаг 3", "Шаг 5". Шаги 3 и 4 поменялись местами из-за того, что "Шаг 3" является отложенным до тех пор, пока не закончится выполнение метода myFunc(), то есть пока не выйдет за пределы видимости.

Вообще, область видимости - это все то, что находится между фигурными скобками {}. И есть два варианта, когда вы используете область видимости: первый - do и второй - внутри цикла.

Блоки do обычно используются вместе с catch для обработки ошибок, но вам это не нужно, потому что вы можете использовать do для создания временной области видимости в любом месте вашего метода. Любые переменные, созданные внутри вашего блока do, существуют только внутри него и любая отложенная работа внутри блока, будет выполнена как только исполнение дойдет до закрывающей фигурной скобки блока.

Например:

print("Шаг 1")

do {
    defer { print("Шаг 2") }
    print("Шаг 3")
    print("Шаг 4")
}

print("Шаг 5")

При запуске этого кода вы увидите 1, 3, 4, 2, 5, потому что 2 отложено до конца блока do.

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

Например:

for i in 1...10 {
    print ("In \(i)")
    defer { print ("Deferred \(i)") }
    print ("Out \(i)")
}

Вы можете использовать defer с несколькими кусками кода

Одной из мощных особенностей defer является то, что вы можете назначать несколько отложенных кусков кода к исполнению и Swift гарантирует их выполнение. Более того, Swift исполняет отложенные куски кода в обратной последовательности. То есть, то, что вы отложили в последнюю очередь, исполнится первым, и наоборот. Таким образом, получается обратный стек.

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

Но есть одна небольшая загвоздка - ваши вызовы defer не должны пробовать покинуть текущую зону видимости при использовании return или, выкидывая ошибку. Все остальное что делается с defer, все к лучшему!

Оригинал http://www.hackingwithswift.com/new-syntax-swift-2-defer