Ответ
Нет, не всегда. Хотя по умолчанию экземпляры классов (reference types) размещаются в куче, компилятор Swift может проводить оптимизации, которые меняют это поведение.
Основные исключения и оптимизации:
- Stack Promotion (продвижение в стек): Если компилятор может доказать, что время жизни объекта ограничено текущей областью видимости (например, локальный объект внутри функции, который никуда не передается), он может разместить его в стеке для повышения производительности.
func processData() { class LocalProcessor { var data: Int = 0 } let processor = LocalProcessor() // Может быть оптимизирован и размещен в стеке // Использование processor } // Объект уничтожается здесь - Оптимизация замыканий: Контекст (captured values) для не-escaping замыканий также может быть размещен в стеке.
Почему это важно? Размещение в стеке значительно быстрее (выделение/освобождение — это просто движение указателя стека), но возможно только при строгих условиях, гарантирующих короткое время жизни объекта. Для объектов, передаваемых между функциями, потоками или имеющих неопределенное время жизни, используется куча.
Ответ 18+ 🔞
А, ну вот, опять этот вечный спор, где что лежит! «Все классы — в куче!» — кричат одни. «Да нихуя!» — отвечают другие. И знаешь, правы вторые, потому что компилятор Свифта — он не дурак, он хитрая жопа, которая постоянно выкрутасы строит.
Смотри, в теории да: взял ты класс, создал экземпляр — бум, он в куче. Но на практике этот ёперный театр оптимизаций начинает такое вытворять, что мама не горюй.
Вот, например, Stack Promotion, или, по-нашему, «продвижение в стек». Суть в чём? Если компилятор видит, что твой объект — местный пацан, который никуда дальше своей функции не сбежит и сдохнет там же, где родился, то зачем ему в куче-то маяться? Выделять память, потом чистить... Овердохуища мороки. Проще запихнуть его прямиком в стек, как какую-нибудь Int или Double. Быстро, чётко, и по завершении функции он сам собой схлопнется.
func doSomeShit() {
class LocalHero { var power: Int = 9000 }
let hero = LocalHero() // Смотри-ка! Вполне может оказаться не в куче, а прямо тут, в стеке!
// ... что-то делаем с героем
} // И тут он благополучно сдыхает, не засоряя кучу.
Второй фокус — с замыканиями. Если твоё замыкание не сбегает (non-escaping), то все значения, которые оно захватило, тоже могут спокойно жить в стеке. Зачем им куча? Они же никуда не денутся, пока функция работает.
А в чём, собственно, прикол-то? Да в скорости, блядь! Стек — это как супермаркет у дома: зашёл, взял, вышел — и всё. Двинул указатель стека — память выделил, двинул обратно — освободил. А куча — это как поездка на оптовый склад на другом конце города: пробки, документы, логистика, ебать-колотить. Поэтому если можно избежать кучи — компилятор избежит. Но! Всё это работает только если объект — скромняга с короткой и предсказуемой жизнью. Если же он начинает шастать между функциями, потоками или жить в неведении («а когда меня, блядь, удалят?»), то тут уж извини — дорога в кучу, друг мой. Туда ему и дорога.