Что такое замыкание (closure) в Swift?

«Что такое замыкание (closure) в Swift?» — вопрос из категории Swift Core, который задают на 23% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Замыкание (closure) — это самодостаточный блок функциональности (функция), который может захватывать и хранить ссылки на переменные и константы из окружающего контекста, даже после того как этот контекст перестал существовать. В Swift замыкания являются first-class типами: их можно передавать как аргументы, возвращать из функций и присваивать переменным.

Пример создания и использования:

// Функция, возвращающая замыкание
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    // Замыкание захватывает runningTotal и amount
    let incrementer: () -> Int = {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementByTwo = makeIncrementer(forIncrement: 2)
print(incrementByTwo()) // 2
print(incrementByTwo()) // 4
// Каждое замыкание хранит свое собственное состояние runningTotal
let anotherIncrementer = makeIncrementer(forIncrement: 2)
print(anotherIncrementer()) // 2 (независимое состояние)

Ключевые особенности и best practices:

  • Захват по ссылке: Захваченные переменные (как runningTotal) хранятся в куче, что позволяет замыканию жить дольше своей области видимости.
  • Циклы сильных ссылок (Retain Cycles): Замыкания, захватывающие self или экземпляры классов, могут создавать циклы удержания памяти. Для их предотвращения используйте списки захвата:

    class MyViewController {
        var handler: (() -> Void)?
    
        func setupHandler() {
            // Используйте [weak self] или [unowned self]
            handler = { [weak self] in
                guard let self = self else { return }
                self.doSomething()
            }
        }
    }
  • Синтаксический сахар: Swift предлагает упрощенный синтаксис (trailing closures, shorthand argument names $0, $1).
  • Автозамыкания (@autoclosure): Позволяют отложить выполнение выражения, оборачивая его в замыкание автоматически. Часто используется для условной оценки (например, в assert).
  • @escaping: Помечает замыкание, которое может быть вызвано после возврата из функции (например, в асинхронных колбэках).