Замыкания и функции как значения
Замыкание — это функция без имени, которую можно хранить в переменной и передавать как аргумент. На замыканиях держится вся реактивность SwiftUI.
Суть урока: в Swift функции — полноценные значения. Их можно присваивать, передавать и возвращать. Замыкание — это анонимная функция, которая ещё и запоминает окружение, в котором создана.
Мы уже встречали замыкания в map и filter. Теперь разберёмся подробнее. Замыкание можно сохранить в переменную:
let greet: (String) -> String = { name in
"Привет, \(name)"
}
print(greet("Анна")) // вызываем как функциюТип (String) -> String читается как «принимает String, возвращает String». Когда замыкание — последний аргумент функции, его выносят за скобки. Это trailing closure — то, что вы постоянно видите в SwiftUI:
func perform(times: Int, action: () -> Void) {
for _ in 0..<times { action() }
}
perform(times: 3) {
print("Тик")
} // замыкание вынесено за скобкиЗамыкание захватывает переменные из окружающего контекста — продолжает видеть их даже после того, как внешняя функция завершилась. Это и есть суть слова «замыкание»:
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1 // захватили count
return count
}
}
let next = makeCounter()
print(next()) // 1
print(next()) // 2 — состояние сохранилосьВнешняя функция makeCounter +-------------------+ | var count = 0 | | ^ | | | захват | | +-----------+ | | | замыкание |----+--> помнит count даже после выхода | +-----------+ | +-------------------+
Попробуй сам ▶ — запусти код прямо в браузере (Pyodide). Здесь нет Swift, но логика та же, что под капотом мобильного кода:
# Замыкания с захватом контекста есть и в Python (вложенные функции).
def make_counter():
count = 0
def inc():
nonlocal count # как захват переменной в Swift
count += 1
return count
return inc
next_id = make_counter()
print(next_id()) # 1
print(next_id()) # 2 — состояние живёт в замыканииКак работает под капотом
Когда замыкание захватывает переменную, Swift сохраняет ссылку на неё (а не копию), поэтому изменения видны и снаружи, и внутри. Если замыкание переживает функцию (например, сохраняется в свойстве), оно помечается @escaping. Захват по ссылке создаёт риск циклов сильных ссылок — их разрывают списком захвата [weak self], о чём поговорим при изучении классов и асинхронности.
Частые ошибки
- Сильный захват self. В escaping-замыканиях это ведёт к утечкам памяти; используйте [weak self].
- Путать вызов и передачу.
action— это само замыкание,action()— его вызов. - Сложный многострочный trailing closure без имён аргументов. $0, $1 в большом блоке снижают читаемость.
Best practices
- Используйте trailing closure для последнего аргумента — это идиоматично и читаемо.
- Указывайте [weak self] в долгоживущих замыканиях, ссылающихся на класс.
- Для коротких замыканий применяйте сокращения $0; для длинных — именуйте аргументы.
Итоги. Замыкания — это функции-значения, запоминающие своё окружение. Они позволяют передавать поведение как данные и лежат в основе обработчиков событий и реактивности SwiftUI. Освоив их, вы поймёте, как кнопка «знает», что делать по нажатию.
Шире контекста
Замыкания — это клей, которым держится всё событийное и асинхронное программирование в iOS. Кнопка в SwiftUI хранит замыкание-действие; модификатор .task принимает асинхронное замыкание; обработчики жестов, анимаций и таймеров — всё это замыкания. Понимание захвата контекста особенно важно при работе с классами и асинхронностью: если долгоживущее замыкание сильно удерживает self, а self удерживает это замыкание, возникает цикл сильных ссылок, и память не освобождается. Спасает список захвата [weak self], который мы подробно разберём дальше. Пока же главное — увидеть, что функция в Swift это полноценное значение: её можно положить в переменную, передать в массив, вернуть из другой функции. Эта идея «поведение как данные» — фундамент, на котором стоит реактивность всего фреймворка, и чем раньше она станет интуитивной, тем легче пойдут следующие разделы курса.