← Назад к вопросам

Замыкания это reference type или value type?

1.3 Junior🔥 211 комментариев
#Язык Swift

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Замыкания в Swift: Reference Type

Замыкания в Swift являются reference type (ссылочным типом). Это фундаментальное свойство, которое отличает их от value types (значимых типов), таких как структуры (struct) и перечисления (enum), и сближает с классами (class).

Почему замыкания — reference type?

Когда вы присваиваете замыкание новой константе или переменной или передаете его в качестве аргумента функции, вы не создаете его новую копию. Вместо этого вы копируете ссылку на уже существующий экземпляр замыкания в памяти (heap). Несколько ссылок могут указывать на один и тот же экземпляр замыкания.

// Создаем замыкание, захватывающее переменную
var counter = 0
let incrementer: () -> Int = {
    counter += 1
    return counter
}

// Присваиваем замыкание новой переменной (копируется ссылка, не значение)
let anotherReferenceToIncrementer = incrementer

// Вызываем через оригинальную ссылку
print(incrementer()) // Вывод: 1
// Вызываем через новую ссылку — состояние общего захваченного значения изменилось
print(anotherReferenceToIncrementer()) // Вывод: 2
// Снова через оригинал — видим продолжающееся изменение
print(incrementer()) // Вывод: 3

В этом примере incrementer и anotherReferenceToIncrementer ссылаются на один и тот же объект замыкания в памяти. Все вызовы влияют на общее захваченное значение counter.

Механизм захвата (capture semantics) и ссылочная природа

Ссылочная природа особенно важна при захвате переменных из окружающего контекста. Замыкание не копирует значения захватываемых переменных на момент создания (если только явно не указано иное), а хранит ссылки на них, что позволяет изменять их после создания замыкания.

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)
// Замыкание incrementByTwo в куче хранит ссылки на runningTotal и amount
print(incrementByTwo()) // 2
print(incrementByTwo()) // 4 - состояние runningTotal сохраняется между вызовами

let alsoIncrementByTwo = incrementByTwo // Копируется ссылка
print(alsoIncrementByTwo()) // 6 - обе ссылки влияют на одно и то же состояние

Сравнение с value type

Для контраста, значимые типы копируются при присваивании или передаче. Каждая переменная хранит свою независимую копию данных.

// Value type пример
struct ValueIncrementer {
    private var counter = 0
    mutating func increment() -> Int {
        counter += 1
        return counter
    }
}

var original = ValueIncrementer()
var copy = original // Происходит КОПИРОВАНИЕ всего значения

print(original.increment()) // 1
print(copy.increment())     // 1 (не 2!) - это независимая копия

Последствия и важные аспекты

  1. Циклы сильных ссылок (Retain Cycles): Поскольку замыкания — reference type, они могут создавать сильные ссылки на объекты, которые сами хранят ссылки на эти замыкания. Это классическая проблема retain cycle. Для ее решения используются списки захвата ([weak self], [unowned self]).

    class SomeViewController {
        var closure: (() -> Void)?
    
        init() {
            // БЕЗ [weak self] это создаст retain cycle!
            closure = { [weak self] in
                self?.doSomething() // self - опциональный weak
            }
        }
    
        func doSomething() { print("Done") }
    }
    
  2. Семантика захвата по умолчанию: Захваченные переменные ссылочных типов (например, экземпляры классов) также захватываются по сильной ссылке, если не указано иное, что может продлить их время жизни.

  3. Использование в concurrent среде: Ссылочная природа требует осторожности при использовании замыканий в многопоточном коде, так как изменение захваченного состояния из разных потоков через одну ссылку может привести к состоянию гонки (data race). Требуется синхронизация (например, с помощью actors или locks).

Исключение: Не-сбегающие замыкания (Non-escaping)

Важно отметить, что семантика non-escaping замыканий (используемых синхронно в рамках тела функции) оптимизируется компилятором. Хотя формально они остаются reference type, компилятор может размещать их в стеке (stack) и избегать накладных расходов на выделение памяти в куче, что является важной оптимизацией производительности. Однако с точки зрения языка программирования это не меняет их основную классификацию.

Вывод: Понимание того, что замыкания являются reference type, критически важно для корректного управления памятью, предотвращения утечек и написания безопасного многопоточного кода в Swift. Их поведение при захвате переменных и возможность создания нескольких ссылок на один экземпляр — прямое следствие их ссылочной природы.

Замыкания это reference type или value type? | PrepBro