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

Объясните работу замыканий (closures) в Swift: отличие от функций, особенности захвата переменных, возможные проблемы при неправильном использовании.

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

Ответ

Закрытия (closures) в Swift — это самостоятельные блоки кода, которые могут захватывать и сохранять ссылки на переменные и константы из окружающего контекста. Они позволяют организовать callback'и, асинхронную обработку и хранить исполнение кода как значения переменных.

Отличия от функций

  • Замыкания могут захватывать переменные из внешней области видимости.
  • Имеют более компактный синтаксис.
  • Могут быть переданы как значения.

Пример замыкания:

var counter = 0 let incrementer: () -> Void = { counter += 1 } incrementer() print(counter) // 1

Особенности захвата

  • По умолчанию переменные захватываются strong (по сильной ссылке).
  • Можно явно определять правила захвата через capture list ([weak self], [unowned self]).

Проблемы

  • Основная проблема — возможные retain cycles при захвате self внутри closure.
  • В многопоточном коде можно незаметно захватить устаревшие значения переменных.

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

Чем отличается strong-capture от использования [weak self] или [unowned self] в capture list closure? Чем это чревато?

Ответ: Если вы не укажете [weak self] или [unowned self], closure по умолчанию захватит self по сильной ссылке, что приведет к retain cycle, если closure и self ссылаются друг на друга. [weak self] захватывает self по слабой ссылке (optional), [unowned self] — по неконтролируемой ссылке (non-optional). Неправильный выбор или отсутствие capture list приводит к утечкам памяти или крашам.

class Test { var closure: (() -> Void)? func setup() { closure = { [weak self] in self?.doWork() } } func doWork() { ... } }

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


История

Программист не использовал capture list в closure внутри UIViewController, который запускал асинхронную загрузку данных. В результате controller и его view не освобождались, что приводило к memory leak после закрытия (dismiss). Анализ инфраструктуры показал сотни "зависших" в памяти контроллеров.


История

Closure захватывал self с помощью [unowned self], но при этом жизненный цикл self завершался раньше closure. Получился краш при обращении к уже освобожденному объекту — приложение вылетало при попытке работы с self inside closure.


История

На проекте внутри вложенной closure захватили две переменные: одну по strong, вторую по weak. Оказалось, что weak-ссылка обнулялась слишком рано, и данные, необходимые для работы closure, были потеряны — баг проявился только в stress-test при нагрузке в многопоточной среде.