Как в Swift поместить экземпляр структуры (value type) в кучу (heap)?

Ответ

Структуры в Swift по умолчанию хранятся на стеке. Однако есть сценарии, когда их необходимо разместить в куче: для продления времени жизни, передачи по ссылке или совместимости с C API. Почему это может понадобиться: Для создания изменяемого shared state или при работе с низкоуровневыми API.

1. Использование класса-обёртки (Boxing) — безопасный и идиоматичный способ

struct Point { var x, y: Int }

// Класс-обёртка хранит значение в куче
final class Box<T> {
    var value: T
    init(_ value: T) { self.value = value }
}

// Создаём структуру в куче
let pointInHeap = Box(Point(x: 10, y: 20))
// Меняем значение (оригинальная структура изменяется)
pointInHeap.value.x = 30
print(pointInHeap.value) // Point(x: 30, y: 20)

// Несколько ссылок на одну область памяти
let anotherReference = pointInHeap
anotherReference.value.y = 40
print(pointInHeap.value.y) // 40 (изменение видно через первую ссылку)

Этот подход управляется ARC и безопасен.

2. Прямое ручное управление памятью (Unsafe-операции) — для экспертов

struct Pixel { var r, g, b, a: UInt8 }

// 1. Выделяем память в куче под одну структуру Pixel
let heapPointer = UnsafeMutablePointer<Pixel>.allocate(capacity: 1)
// 2. Инициализируем память значением
heapPointer.initialize(to: Pixel(r: 255, g: 0, b: 0, a: 255))

// 3. Используем память
heapPointer.pointee.g = 128 // Изменяем значение через указатель
print(heapPointer.pointee) // Pixel(r: 255, g: 128, b: 0, a: 255)

// 4. КРИТИЧЕСКИ ВАЖНО: Освобождаем в обратном порядке
heapPointer.deinitialize(count: 1) // Деинициализируем (вызываем деинициализаторы)
heapPointer.deallocate() // Освобождаем память

⚠️ Предупреждение: Unsafe API не управляется ARC. Пропуск deinitialize/deallocate ведёт к утечке памяти, а доступ после освобождения — к сбою. Используйте только при крайней необходимости (взаимодействие с C) и оборачивайте в defer.

Итог: Для большинства задач используйте класс-обёртку (Box). Прямые Unsafe операции оставьте для системного программирования.

Ответ 18+ 🔞

Давай разберёмся с этим вопросом, как есть. Ситуация, блядь, такая: структуры в Swift — они по умолчанию на стеке живут, быстрые, локальные, чистота, красота. Но жизнь, сука, сложная штука. Иногда их нахуй в кучу запихнуть надо. Зачем? Ну, например, чтобы несколько твоих объектов на одну и ту же хуйню ссылались, или чтобы с каким-нибудь древним C-шным кодом подружить, который только указатели понимает.

1. Спокойный, цивилизованный способ — завернуть в коробку (Boxing)

Представь, что твоя структура — это хрупкий хер, который надо в прочную коробку запаковать, чтобы по ссылкам передавать. Коробка — это класс, а он уже живёт в куче. Идиоматично, безопасно, ARC всё за тебя приберёт.

struct Point { var x, y: Int }

// Вот эта самая коробка. Final, чтобы ни одна сука не наследовалась.
final class Box<T> {
    var value: T
    init(_ value: T) { self.value = value }
}

// Запаковываем структуру — вуаля, она уже в куче!
let pointInHeap = Box(Point(x: 10, y: 20))
// Меняем значение внутри коробки
pointInHeap.value.x = 30
print(pointInHeap.value) // Point(x: 30, y: 20)

// А теперь магия: создаём вторую ссылку на ту же самую коробку.
let anotherReference = pointInHeap
anotherReference.value.y = 40
// Смотри-ка, блядь, через первую ссылку изменение видно!
print(pointInHeap.value.y) // 40

Вот и всё, ёпта. Никаких танцев с бубном, ARC сам коробку сожрёт, когда все на неё забудут. Красота.

2. Способ для отчаянных — ручное управление памятью (Unsafe)

А вот это, сука, уже для тех, кто считает себя круче компилятора. Тут тебе прямой доступ к куче, как в старые добрые C-шные времена. Ошибёшься — получишь утечку памяти или креш, который тебе ебальник разнесёт.

struct Pixel { var r, g, b, a: UInt8 }

// 1. Тыкаем пальцем в небо и кричим: "Дай мне память в куче под один Pixel!"
let heapPointer = UnsafeMutablePointer<Pixel>.allocate(capacity: 1)
// 2. Теперь эту память надо инициализировать. Не инициализируешь — там мусор, обращение к нему — пиздец.
heapPointer.initialize(to: Pixel(r: 255, g: 0, b: 0, a: 255))

// 3. Работаем с этой памятью через указатель.
heapPointer.pointee.g = 128 // Меняем зелёный канал
print(heapPointer.pointee) // Pixel(r: 255, g: 128, b: 0, a: 255)

// 4. А ВОТ ТУТ, БЛЯДЬ, САМОЕ ВАЖНОЕ! УБИРАЕМ ЗА СОБОЙ!
heapPointer.deinitialize(count: 1) // Сначала говорим: "Структура, прощай, вызывай деинициализаторы!"
heapPointer.deallocate() // А потом уже: "Память, свободна!"

⚠️ Внимание, ёпта! Если забудешь deinitialize или deallocate — память потечёт, как из дырявого ведра. Если обратишься к памяти после освобождения — получишь неопределённое поведение, а это хуже, чем просто креш. Используй это только если ты реально знаешь, что делаешь (например, пишешь биндинг к C-библиотеке), и всегда оборачивай в defer, чтобы не забыть почистить.

Итог, блядь:
Хочешь просто и безопасно — бери Box, заворачивай структуру и спи спокойно.
Чувствуешь в себе силы ебашить с памятью вручную, как какой-нибудь системный маг — ну что ж, UnsafeMutablePointer к твоим услугам, но помни: один неверный шаг, и ты в мире неопределённого поведения, где компилятор имеет право выебать твой код как хочет.