История вопроса:
Escape-анализ — термин из оптимизации компиляторов и управления памятью. В Swift он важен из-за активного использования closure и ARC. Связан с понятием escaping и non-escaping closure, определяющих могут ли замыкания выходить за рамки области видимости функции.
Проблема:
Неправильное определение типа замыкания приводит к ошибкам владения памятью, утечкам, неожиданным захватам переменных и просадке производительности. Нужно четко понимать когда closure "уходит" (escapes), а когда нет, и корректно помечать их.
Решение:
Swift требует явно помечать escaping closure атрибутом @escaping. Non-escaping closure могут быть захвачены только внутри функции, они автоматически обладают более эффективным управлением памятью и могут использовать захваченные переменные безопаснее.
Пример разницы:
// non-escaping closure (более быстрый, не хранится после вызова) func performSync(block: () -> Void) { block() } // escaping closure (может быть сохранен и выполнен позже) var storedCompletion: (() -> Void)? func performAsync(block: @escaping () -> Void) { storedCompletion = block }
Ключевые особенности:
Можно ли изменить closure, определённый как non-escaping, на escaping без изменений исходного кода функции?
Нет. Атрибут @escaping должен быть явно указан в сигнатуре функции. Если closure внутри функции передается за её пределы — необходим только escaping closure, иначе компилятор выдаст ошибку.
Безопасно ли передавать self внутрь escaping closure без weak/unowned захвата?
Нет. Escaping closure потенциально может создать retain cycle, если захватывает self сильной ссылкой. Нужно явно распоряжаться захватом:
someAsync { [weak self] in self?.doSomething() }
Всегда ли escaping closure является глобальным (хранится в памяти до конца программы)?
Нет. Его жизненный цикл зависит от области хранения. Если closure сохранён только временно или свойство занулено, он будет освобождён как только свойство потеряет владельца. Глобальными становятся только те closure, которые несут ссылки на глобальные объекты или их хранят в глобальных переменных.
Внутри метода класса сохраняют closure без @escaping (ошибка компилятора), позже исправляют, забывают про защиту от retain cycle — приложение утечка в памяти.
Плюсы:
Минусы:
Разработчик всегда проверяет где требуется escaping closure, захватывает self как weak/unowned, избавляется от утечек, работает с памятью безопасно.
Плюсы:
Минусы: