Какой тип у замыкания, захватывающего переменные из внешнего контекста?

«Какой тип у замыкания, захватывающего переменные из внешнего контекста?» — вопрос из категории Swift Core, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Тип замыкания не меняется от факта захвата переменных. Захват (capture) влияет на поведение и время жизни переменных, но не на сигнатуру типа замыкания. Тип по-прежнему определяется только параметрами и возвращаемым значением: (Параметры) -> ВозвращаемыйТип.

Пример: Замыкание с захватом:

var counter = 0

// Тип `incrementer` — `() -> Void`, несмотря на захват `counter`
let incrementer: () -> Void = {
    counter += 1 // Захват переменной `counter` по ссылке
    print("Counter is now (counter)")
}

incrementer() // Counter is now 1
incrementer() // Counter is now 2

Важные аспекты захвата:

  1. Сильный захват по умолчанию: Захваченные переменные удерживаются (strong reference), что может создать цикл сильных ссылок.
  2. Список захвата (capture list): Позволяет явно управлять захватом.
    var value = 10
    let closure = { [value] in // Захват КОПИИ значения на момент создания замыкания
        print("Captured value: (value)")
    }
    value = 20
    closure() // Выведет: Captured value: 10
  3. Захват self: В замыканиях внутри классов нужно избегать циклов удержания.
    class MyClass {
        var property = "Hello"
        lazy var closure: () -> Void = { [weak self] in // Слабый захват self
            print(self?.property ?? "Object deallocated")
        }
    }

Вывод: Захват — это семантическая особенность замыкания, а не часть его типа. Тип (Int) -> String остается таким, независимо от того, какие переменные замыкание захватывает.