← Назад к вопросам

Что делает ключевое слово defer в Swift?

2.0 Middle🔥 182 комментариев
#CI/CD и инструменты разработки#Soft Skills и карьера

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основное назначение ключевого слова defer

Ключевое слово defer в Swift используется для определения блока кода, который будет выполнен обязательно и перед выходом из текущей области видимости, независимо от того, как именно происходит этот выход — через нормальное выполнение, досрочный возврат (return), выброс ошибки (throw) или даже аварийное завершение.

Как работает defer

Блок кода внутри defer выполняется в обратном порядке относительно их объявления (LIFO — Last In, First Out). Это особенно важно, когда в функции используется несколько defer-блоков.

func exampleDeferOrder() {
    defer { print("Первый defer - выполнится третьим") }
    defer { print("Второй defer - выполнится вторым") }
    print("Основной код - выполнится первым")
}

exampleDeferOrder()
// Вывод:
// Основной код - выполнится первым
// Второй defer - выполнится вторым
// Первый defer - выполнится третьим

Основные сценарии использования

1. Управление ресурсами (память, файлы, сетевые соединения)

Наиболее распространённое применение — гарантированное освобождение ресурсов даже при возникновении ошибок.

func readFile(filename: String) throws -> String {
    let file = openFile(filename) // Открываем ресурс
    defer {
        closeFile(file) // Закрываем ВСЕГДА, даже если будет ошибка
    }
    
    if filename.isEmpty {
        throw FileError.invalidName // defer выполнится ПЕРЕД выбросом ошибки!
    }
    
    return try readContent(file)
}

2. Изменение состояния с гарантированным возвратом к исходному

Часто используется в UI-коде для временного изменения состояния интерфейса.

func animateWithTemporaryState() {
    view.isUserInteractionEnabled = false // Отключаем взаимодействие
    defer {
        view.isUserInteractionEnabled = true // Восстанавливаем ВСЕГДА
    }
    
    // Длительная анимация или операция
    UIView.animate(withDuration: 1.0) {
        // анимация...
    }
}

3. Логирование и отслеживание времени выполнения

Удобно для измерения производительности блоков кода.

func performComplexCalculation() -> Result {
    let startTime = Date() // Засекаем время
    defer {
        let duration = Date().timeIntervalSince(startTime)
        print("Выполнение заняло \(duration) секунд")
    }
    
    // Сложные вычисления...
    return calculateResult()
}

Важные особенности и ограничения

Порядок выполнения имеет значение

func demonstrateOrder() {
    var value = "начальное"
    
    value = "изменённое"
    defer { print("Defer 1: \(value)") }
    
    defer {
        value = "финальное"
        print("Defer 2: \(value)")
    }
}
// Defer 2 выведет "финальное"
// Defer 1 выведет "финальное" (так как value уже изменён)

defer не работает с брейкпоинтами и fatalError

Если выполнение прерывается аппаратно (сигнал) или через fatalError(), defer-блоки не выполнятся.

Не используйте defer для кода, который может бросить ошибку

Это усложняет обработку ошибок и может скрыть первоначальную ошибку.

// ❌ ПЛОХО
defer {
    try? cleanup() // Скрываем возможные ошибки
}

// ✅ ЛУЧШЕ
do {
    try performOperation()
} finally {
    cleanup() // Явная обработка
}

Практический пример из реальной разработки

class DatabaseManager {
    func executeTransaction(_ operations: [() throws -> Void]) throws {
        beginTransaction() // Начинаем транзакцию
        defer {
            // Этот код выполнится в любом случае:
            // при успехе, ошибке или досрочном возврате
            endTransaction() // Завершаем транзакцию
            print("Транзакция завершена")
        }
        
        for operation in operations {
            try operation() // Если здесь ошибка — defer всё равно выполнится
        }
        
        commitTransaction() // Фиксируем изменения
    }
}

Ключевые выводы

  1. defer обеспечивает выполнение кода при ЛЮБОМ выходе из области видимости
  2. Порядок выполнения — обратный объявлению (последний объявленный — первый выполнится)
  3. Идеально подходит для:
    • Освобождения ресурсов (файлы, память, соединения)
    • Восстановления исходного состояния
    • Логирования и телеметрии
  4. Не подходит для:
    • Кода, который должен выполняться только при успешном завершении
    • Обработки ошибок (используйте do-catch)
    • Контроля потока выполнения

Использование defer делает код более надёжным и читаемым, явно разделяя основную логику и необходимую "уборку", что особенно важно в Swift с его акцентом на безопасность и предсказуемость выполнения.