В каких случаях value-тип в Swift размещается в куче (heap), а не в стеке (stack)?

«В каких случаях value-тип в Swift размещается в куче (heap), а не в стеке (stack)?» — вопрос из категории Управление памятью, который задают на 23% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Хотя value-типы (структуры, перечисления, кортежи) по умолчанию размещаются в стеке, что обеспечивает быстрый аллокейт и деаллокейт, существует несколько сценариев, когда они или их содержимое оказываются в куче:

1. Захват escaping-замыканием Когда value-тип захватывается escaping-замыканием (которое может быть вызвано позже, чем завершится функция), система вынуждена аллоцировать память в куче, чтобы обеспечить время жизни захваченных значений.

func createEscapingClosure() -> () -> Void {
    var localCounter = 0 // Int (value type, изначально в стеке)

    let closure = {
        localCounter += 1
        print(localCounter)
    }
    // Замыкание `closure` является escaping (возвращается из функции).
    // `localCounter` будет перемещен в кучу для совместного использования.
    return closure
}
let savedClosure = createEscapingClosure()
// Область видимости функции createEscasingClosure завершилась,
// но captured `localCounter` жив в куче.
savedClosure() // 1
savedClosure() // 2

2. Является свойством reference-типа (класса) Value-типы, хранящиеся как свойства экземпляра класса, размещаются вместе с этим экземпляром в куче.

class MyClass {
    var myPoint = CGPoint(x: 0, y: 0) // CGPoint (struct) находится в куче внутри экземпляра MyClass
}

3. Приведение к протокольному типу (Existential Container) Когда value-тип присваивается переменной протокольного типа, система использует existential container, который может хранить небольшие значения внутри себя (inline), но для больших значений (более 3-х слов) использует аллокацию в куче.

protocol Drawable {}
struct LargeStruct: Drawable { // Большая структура (например, > 24 байт)
    var data: (Int, Int, Int, Int, Int, Int, Int) = (1,2,3,4,5,6,7)
}

let value = LargeStruct() // Создается в стеке
let boxed: Drawable = value // Может привести к аллокации в куче для хранения

4. Внутренняя реализация "copy-on-write" (CoW) коллекций Стандартные библиотечные коллекции (Array, String, Dictionary, Set) являются value-типами, но для эффективности хранят свои буферы данных в куче. При копировании копируется только ссылка на буфер. Реальное копирование данных (аллокация нового буфера в куче) происходит только при модификации (write) одной из копий.

var array1 = [1, 2, 3, 4, 5] // Буфер массива аллоцирован в куче
var array2 = array1          // Копируется только ссылка на буфер (дешево)
// Оба массива сейчас указывают на один буфер в куче
array2.append(6)            // Только здесь происходит реальное копирование
                           // (аллокация нового буфера для array2)

Итог: Размещение в куче для value-типов — это оптимизация времени жизни или производительности, необходимая, когда значение должно пережить свою исходную область видимости или когда семантика копирования по значению должна быть реализована эффективно.