Для чего используется ключевое слово `defer` в Swift?

Ответ

Ключевое слово defer откладывает выполнение блока кода до момента выхода из текущей области видимости (функции, цикла, условия, do-блока). Это гарантирует выполнение cleanup-логики, даже если выход произошел из-за ошибки (throw) или досрочного return.

Основные правила:

  1. Порядок выполнения (LIFO): Код в блоках defer выполняется в обратном порядке — последний объявленный defer срабатывает первым.
  2. Гарантия выполнения: Блок defer выполнится при любом выходе из области видимости.

Типичные сценарии использования:

  • Закрытие файловых дескрипторов или сетевых соединений.
  • Освобождение ресурсов (например, снятие блокировки).
  • Логирование завершения работы функции.

Пример:

func processFile(at path: String) throws -> String {
    let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
    defer {
        // Этот код выполнится в самом конце функции,
        // независимо от того, как она завершится.
        fileHandle.closeFile()
        print("Файл закрыт.")
    }

    let data = fileHandle.readDataToEndOfFile()
    guard let content = String(data: data, encoding: .utf8) else {
        throw FileError.invalidEncoding // defer сработает ПЕРЕД выбросом ошибки!
    }

    return content
}

Важно: defer не предназначен для управления потоком выполнения (как if/else). Его цель — обеспечить предсказуемую очистку ресурсов.

Ответ 18+ 🔞

А, ну вот, опять этот ваш defer! Слушай, представь себе самого ответственного уборщика в мире, который появляется ровно в тот момент, когда ты уже одной ногой за дверью, чтобы всё за тобой прибрать. Вот это он и есть, ёпта.

Если по-простому, то ты говоришь: «Эй, Swift, вот этот кусок кода — ты его выполни в самый последний момент, когда из этой функции (или цикла, или ещё откуда) будем вываливаться. Неважно, как мы вывалимся — штатно, через return, или нас отсюда вышибет ошибкой (throw), или просто кончится область видимости. В любом случае — сделай это, блядь!»

Главные законы этого подземного царства:

  1. Очередь на выход (LIFO): Кто последний пришёл — тот первый на выход. Объявил три defer'а? Тот, что объявил последним, выполнится первым. Как будто стопку тарелок разбираешь — верхнюю снимаешь.
  2. Железобетонная гарантия: Этот код выполнится при любом, блядь, раскладе. Даже если в середине функции всё полетит к чертям собачьим и выскочит ошибка — твой defer отработает, как швейцарские часы, прямо перед тем, как тебя выкинет из функции с этим самым throw.

Где это, сука, пригождается?

  • Закрыть файл, который открыл. А то он так и будет висеть, как забытый кран.
  • Отпустить какую-нибудь блокировку (lock), а то все остальные потоки будут ждать, пока синий экран не придет.
  • Написать в лог, что функция, типа, закончила свои страдания.

Смотри, как это выглядит в дикой природе:

func processFile(at path: String) throws -> String {
    // Открыли файл, всё серьёзно
    let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))

    defer {
        // А вот и наш магический уборщик! Он встал в очередь.
        // Он закроет файл в ЛЮБОМ случае. Прямо перед самым выходом.
        fileHandle.closeFile()
        print("Файл закрыт. Можно выдыхать.")
    }

    // Пытаемся что-то прочитать
    let data = fileHandle.readDataToEndOfFile()
    guard let content = String(data: data, encoding: .utf8) else {
        // Ой, всё! Кодировка кривая! Кидаем ошибку!
        // Но стоп! Перед тем как нас вышвырнет с этой ошибкой,
        // Swift вспомнит: "А, точно, там же defer висит!".
        // Сначала ЗАКРОЕТ ФАЙЛ, напечатает сообщение, и только ПОТОМ выбросит ошибку наружу.
        throw FileError.invalidEncoding
    }

    // Всё ок, возвращаем результат
    return content // И тут, перед самым возвратом, defer тоже сработает! Файл закроется.
}

Важное замечание, чтобы не обосраться: defer — это не замена для if или switch. Это не инструмент для логики, это инструмент для предсказуемой уборки, блядь. Ты им не управляешь потоком, ты им гарантируешь, что за собой говна не оставишь. Всё.