Есть ли исключения из правил хранения value type в стеке и reference type в куче
Что проверяют
Понимаете ли вы, что «структуры на стеке, классы в куче» это упрощение, а не правило языка. Swift обещает семантику значений и ссылок, а не место хранения. Следом обычно спрашивают про copy-on-write и про то, почему большая структура бывает медленнее класса.
Как отвечать
Скажите главное: размещение это деталь реализации, которую решает компилятор. Затем приведите исключения в обе стороны. Value type оказывается в куче:
- поле класса: структура внутри экземпляра класса лежит прямо в его памяти, то есть в куче;
- захват замыканием: локальная
var, захваченная@escaping-замыканием, переезжает в контейнер в куче, иначе она умерла бы вместе с кадром стека; - экзистенциальный контейнер: значение
any Protocolхранится в буфере на три машинных слова, для структуры крупнее выделяется память в куче; indirect enum: рекурсивные case хранятся в отдельной ячейке в куче;- коллекции:
Array,Dictionary,SetиStringсами структуры, но элементы лежат в буфере в куче, а копирование работает по copy-on-write. Короткие строки на 64-битных платформах хранятся прямо в значении; - async-функции: локальные переменные, живущие через
await, хранятся не на стеке потока, а во фрейме задачи.
Обратный случай: оптимизатор может разместить экземпляр класса на стеке, если доказал, что ссылка не покидает функцию. Гарантий тут нет. Ещё пример: при мосте в NSArray значение Int превращается в объект NSNumber.
Вывод: выбирать между struct и class стоит по семантике, то есть нужна ли общая изменяемая идентичность, а не по мнимой разнице стека и кучи.
Где спотыкаются
Называют Array ссылочным типом, потому что его буфер в куче. Это структура с семантикой значения, копия независима от оригинала.
Приравнивают value type к стеку и делают выводы о скорости. Структура с десятком полей-ссылок при каждом копировании делает retain и release для каждого из них.
Считают, что copy-on-write есть у любой своей структуры. Он реализован в стандартных коллекциях, а для своего типа его пишут вручную через isKnownUniquelyReferenced.