Ответ
Тип-значение (структура или перечисление) по умолчанию хранится в стеке, но попадает в кучу (heap) в следующих сценариях:
-
Когда он является свойством класса: Все содержимое экземпляра класса размещается в куче.
class MyClass { var myStruct: MyStruct // MyStruct хранится в куче как часть MyClass. } -
При захвате замыканием с продлённым временем жизни: Если замыкание, захватившее структуру, является
@escapingили сохраняется, значение переносится в кучу. -
При работе с механизмом Copy-on-Write (CoW): Большие типы-значения (как
Array,String,Dictionary) используют кучу для хранения своих буферов. При копировании копируется только ссылка на буфер, а реальное копирование данных в новую область кучи происходит лишь при изменении (write).var array1 = [1, 2, 3] // Буфер массива в куче. var array2 = array1 // Копируется только ссылка (CoW). Память в куче общая. array2.append(4) // Теперь происходит реальное копирование буфера в новое место кучи. -
При использовании
indirectдля перечислений: Ключевое словоindirectзаставляет хранить связанное значение перечисления в куче, позволяя рекурсивные структуры.indirect enum LinkedListNode { case value(Int, next: LinkedListNode) case end }
Ответ 18+ 🔞
А, слушай, смотри, вот тебе на самом деле интересная хуйня, про которую многие думают, что они её понимают. Тип-значение, структура там или енум, он по умолчанию, конечно, в стеке. Прямо там, под носом, быстрый, локальный, красота. Но есть такие подлые сценарии, когда эта, блядь, невинная структурка вдруг пиздует прямиком в кучу, как будто так и надо. И вот почему.
Во-первых, когда она — свойство какого-нибудь ёбанного класса. Ты же помнишь, классы — это ссылочные типы, вся их сущность живёт в куче. Ну так вот, если внутри этого класса затесалась структура, то она автоматически становится частью этой кучи. Как будто её посадили в тюрьму, блядь. Выбора у неё нет.
class MyClass {
var myStruct: MyStruct // MyStruct теперь сидит в куче, как часть MyClass. Смирись.
}
Во-вторых, этот момент с замыканиями, которые сбегают. Ну, @escaping там или просто сохраняются куда-то. Если твоя структура попала в плен к такому замыканию, которое само решило задержаться подольше, то её, бедолагу, тоже выпихивают в кучу, чтобы она не сдохла раньше времени. Захватили — и в ссылочный мир, пиздец.
В-третьих, вот эта ихняя любимая хуйня — Copy-on-Write (CoW). Это вообще гениальная подстава. Большие ребята вроде Array, String или Dictionary — они же тоже типы-значения, да? Но они не дураки, чтобы таскать с собой гигабайты данных по стеку. Они хранят свои настоящие данные — буфер — в куче. А у тебя в руках только билетик, ссылочка на этот буфер. Ты копируешь массив — копируется только билетик, а буфер в куче один на всех. И все довольны, пока ты не решишь один массив изменить. Вот тут-то система и говорит: «Ага, щас, блядь, разошёлся!» — и делает настоящее, дорогое копирование буфера в новое место кучи. Хитрая жопа, одним словом.
var array1 = [1, 2, 3] // Буфер массива уже в куче, ёпта.
var array2 = array1 // Копируем билетик. В куче всё тот же буфер, общий.
array2.append(4) // Опа-на! Изменение! Теперь буфер копируется в новое место кучи. Вот тебе и CoW.
И наконец, четвёртое — indirect для перечислений. Это вообще специальный инструмент для создания рекурсивных структур, которые иначе бы не влезли в себя сами, понимаешь? Ты говоришь indirect — и связанное с кейсом значение летит прямиком в кучу. Потому что иначе как ты впихнёшь LinkedListNode внутрь самого LinkedListNode? Никак, блядь. Только через ссылку, через кучу.
indirect enum LinkedListNode {
case value(Int, next: LinkedListNode) // 'next' теперь не прямо здесь, а где-то там, в куче.
case end
}
Вот и получается, что «тип-значение» — это не всегда про стек. Это про семантику. А где физически лежат данные — это уже второй вопрос, на который иногда ответ — «в пизду, то есть в кучу».