В каких случаях структура (struct) в Swift хранится в куче (heap)?

Ответ

Структуры — это value-типы и по умолчанию хранятся в стеке. Однако они могут быть размещены в куче косвенно, если становятся частью reference-типа или когда этого требует семантика копирования при записи (Copy-on-Write, CoW).

Основные случаи:

  1. Когда структура является свойством класса: Классы (reference-типы) хранятся в куче, и все их свойства, включая структуры, размещаются там же.

    struct Point { var x, y: Int }
    class Box {
        var point: Point // Структура `Point` хранится в куче внутри экземпляра `Box`
    }
  2. Когда структура захватывается escaping-замыканием: Замыкание, которое может пережить контекст, в котором оно создано, хранится в куче и захватывает все используемые в нем значения.

    var storedClosure: (() -> Void)?
    func captureStruct() {
        let myStruct = MyStruct()
        storedClosure = { print(myStruct) } // `myStruct` захватывается и попадает в кучу
    }
  3. При работе с коллекциями, использующими CoW: Стандартные типы, такие как Array, Dictionary, String, хранят свои буферы в куче. При помещении в них структуры, она также окажется там.

    struct Item { var id: Int }
    var array = [Item(id: 1)] // Буфер массива и элемент `Item` находятся в куче.

Вывод: Сама по себе структура не «попадает» в кучу напрямую. Это происходит, когда она становится частью другого объекта, который управляется подсчетом ссылок (класс, escaping-замыкание, CoW-буфер).

Ответ 18+ 🔞

Да ты посмотри, какая интересная хуйня выходит! Вот все кричат: "Структуры — value-типы, они в стеке, быстрые, легкие, красота!" А потом такой чувак приходит, пишет код, и у него всё ебётся нахуй, потому что он не понимает, куда на самом деле эта структура может свалиться.

Смотри, вот тебе три главных пиздеца, когда твоя милая, стековая структурка внезапно оказывается в куче, и начинается подсчёт ссылок, как у какой-то шлюхи-классицы.

Случай первый — классический подлян. Ты создал структуру Point, она вроде как твоя, чистая, в стеке. А потом ты её, блядь, суёшь в класс. А класс — это же ссылочный тип, он живёт в куче, как богатый дядя в особняке. И всё, что у него внутри, тоже там живёт. Твоя Point теперь не девочка с района, а содержанка в золотой клетке кучи.

struct Point { var x, y: Int }
class Box {
    var point: Point // Всё, пидор, поздравляю. Твой `Point` теперь в куче, потому что сам `Box` в куче.
}

Случай второй — коварный, как змея подколодная. Замыкания. Ну, создал ты локальную структурку в функции, подумал — щас вызовется и освободится. Ан нет! Ты её захватил в escaping-замыкание, которое может тебя пережить. Куда это замыкание девается? Правильно, сука, в кучу! И тащит за собой всё, что захватило. Твоя структурка теперь в заложниках у этого замыкания, в самой гуще кучи.

var storedClosure: (() -> Void)?
func captureStruct() {
    let myStruct = MyStruct() // Родилась в стеке, чистая.
    storedClosure = { print(myStruct) } // Ёбта! Попала в плен. Теперь она в куче, вместе с этим замыканием.
}

Случай третий — массовый, системный. Массивы, словари, строки. Они же все умные, используют Copy-on-Write (CoW). То есть пока ты один, они хранят буфер где? В куче, блядь! Положил ты свою структурку в массив — всё, приехали. Она теперь лежит в этом общем буфере, который в куче. И пока массив не скопируют, она там и будет тлеть.

struct Item { var id: Int }
var array = [Item(id: 1)] // Кажется, просто массив? А буфер-то у массива в куче! И твой `Item` теперь там же.

Вывод, ёпта: Сама по себе структура не стремится в кучу, как порядочная девушка не стремится в бордель. Но если её завербовать в банду ссылочных типов (классов, замыканий, CoW-коллекций) — всё, пиши пропало. Она становится частью их системы, их чёрной дыры в куче. Так что думай, куда ты свою структуру суёшь, а то оптимизация твоя нахуй улетит, сама не заметишь как.