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

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

Ответ

Структуры в 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 операции оставьте для системного программирования.