Замыкания и функции как значения

Замыкание — это функция без имени, которую можно хранить в переменной и передавать как аргумент. На замыканиях держится вся реактивность 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 это полноценное значение: её можно положить в переменную, передать в массив, вернуть из другой функции. Эта идея «поведение как данные» — фундамент, на котором стоит реактивность всего фреймворка, и чем раньше она станет интуитивной, тем легче пойдут следующие разделы курса.

Проверьте себя
1. Что особенного делает замыкание по сравнению с обычной функцией?
AРаботает быстрее
BЗахватывает и запоминает переменные из окружающего контекста
CНе может принимать аргументы
DВсегда возвращает nil
2. Что такое trailing closure?
AЗамыкание, которое всегда возвращает Void
BСинтаксис вынесения последнего замыкания-аргумента за круглые скобки
CЗамыкание без захвата
DГлобальная функция