ПрограммированиеiOS разработчик

Опишите механизм работы defer в Swift, с какими граничными случаями и особенностями стоит быть осторожным?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

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

Особенности работы:

  • Если в функции несколько defer, отработают они в обратном порядке (стековая структура LIFO — last in, first out).
  • defer выполняется даже при возникновении ошибки (throw) или при любом return из функции.
  • В closure defer относят к телу closure, а не к месту вызова closure.

Пример:

func testDefer() { print("begin") defer { print("first defer") } defer { print("second defer") } print("end") } // Выведет: // begin // end // second defer // first defer

Граничные случаи:

  • defer захватывает значения переменных на момент появления defer: если в defer closure используем переменные, изменения в них будет видны на момент выполнения defer.
  • Если функция завершила выполнение с fatalError — defer вызван не будет.

Вопрос с подвохом.

Будут ли выполены все defer-блоки в функции, если вызвать внутри неё fatalError?

Ответ: Нет, если в функции вызывается fatalError (или похожие неконтролируемые крэши), то все отложенные через defer блоки не выполняются. defer не гарантирует вызова кода в случаях аварийного завершения работы приложения.

Пример:

func foo() { defer { print("Defer 1") } fatalError("Oops") defer { print("Defer 2") } } foo() // ничего не выведет, сработает crash

Примеры реальных ошибок из-за незнания тонкостей темы.


История

В проекте использовали defer для закрытия файлового дескриптора. При появлении ошибки использовали fatalError вместо корректного throw, что приводило к утечке открытого ресурса, поскольку defer не отрабатывал при crash.


История

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


История

Во вложенном closure написали defer-блок, считая, что он отработает при выходе из внешней функции. В результате этот defer исполнился при выходе из closure, а не всей функции, и ресурс был освобождён слишком рано.