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

Что такое escaping и non-escaping closures в Swift? Как правильно организовать работу с замыканиями, какие нюансы существуют в их использовании, и чем это может грозить при работе с асинхронным кодом?

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

Ответ.

Escaping closure — это замыкание, которое "уходит" из области видимости вызывающей функции (например, сохраняется для асинхронного выполнения).

По умолчанию в Swift функции принимают не-escaping closure: замыкание выполняется в пределах вызова функции.

Для явного указания escaping используется ключевое слово @escaping:

func asyncTask(completion: @escaping () -> Void) { DispatchQueue.main.async { completion() } }

Ключевые отличия:

  • Escaping closures могут храниться и вызываться позже.
  • Для захвата self в escaping closure — требуется явно указывать [weak self] или [unowned self] для предотвращения утечек памяти (retain cycle).

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

Нужно ли всегда писать @escaping для замыканий, передаваемых в параметры функции, вызывающей DispatchQueue.async?

— Да. Так как DispatchQueue.async сохраняет closure до момента выполнения, closure становится escaping.

Пример:

func foo(action: () -> Void) { DispatchQueue.main.async { action() // Не скомпилируется: closure должен быть escaping. } } // Нужно: func bar(action: @escaping () -> Void) { ... }

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


История

Контроллер создал сильную ссылку на self внутри escaping closure (например, сетевой запрос). Контроллер не освобождался после ухода с экрана — сильный retain cycle. Решение: использовать [weak self] или [unowned self].


История

Функция передавала closure в DispatchQueue.async, но не пометила его как escaping. Проект не компилировался, ошибки было сложно найти из-за вложенности функций.


История

Внутри closure обращались к объекту, который уже деинициализировался к моменту вызова closure (использовали [unowned self]). В результате — runtime crash. Решение: использовать [weak self] и проверку на nil.