С этого вопроса начинается почти каждый iOS-собес: «чем struct отличается от class». Ответ «структура на стеке, класс в куче» засчитают как ошибку. Разберёмся, что на самом деле важно.
Семантика: копия или общий объект
В Swift два вида типов:
- Типы значений:
struct,enum, кортежи. Присваивание и передача в функцию дают независимую копию. - Ссылочные типы:
class,actor, замыкания. Переменные указывают на один и тот же объект.
struct PointS { var x: Int }
final class PointC { var x: Int; init(x: Int) { self.x = x } }
var a = PointS(x: 1)
var b = a
b.x = 5
print(a.x) // 1: у b своя копия
let c = PointC(x: 1)
let d = c
d.x = 5
print(c.x) // 5: объект один
Почти вся стандартная библиотека построена на структурах: Int, String, Array, Dictionary, Set, Optional. Поэтому массив, переданный в функцию, не изменится «сзади».
let и var
Для структуры let замораживает всё значение, включая var-свойства. Для класса let фиксирует только ссылку: перенаправить переменную нельзя, а менять свойства объекта можно.
let s = PointS(x: 1)
// s.x = 2 ошибка компиляции
let o = PointC(x: 1)
o.x = 2 // можно
mutating
Методы структуры по умолчанию не могут менять self. Метод, который меняет свойства, помечают mutating, и его нельзя вызвать у константы.
struct Counter {
var value = 0
mutating func increment() { value += 1 }
}
Внутри mutating можно даже присвоить self целиком, так часто пишут переключатели у enum.
Copy-on-write
Если бы массив на миллион элементов копировался при каждом присваивании, Swift был бы медленным. Поэтому Array, Dictionary, Set и String используют copy-on-write: после var b = a обе переменные делят один буфер, а копия происходит при первой мутации, если буфер не уникален. Проверка уникальности делается через isKnownUniquelyReferenced.
Свои структуры этого не умеют. Если у вас структура с большим хранилищем, copy-on-write пишут вручную: приватный класс-хранилище и проверка уникальности в мутирующих методах.
Ловушка: класс внутри структуры
Копия структуры поэлементная. Если поле структуры это класс, копируется ссылка, и обе копии работают с одним объектом:
final class Box { var value = 0 }
struct Wrapper { var box = Box() }
var w1 = Wrapper()
var w2 = w1
w2.box.value = 10
print(w1.box.value) // 10
То же с массивом объектов: сам массив копируется, а элементы остаются общими.
Где живут значения
Семантика не равна размещению. Локальная структура обычно в стеке или регистрах, но её данные окажутся в куче, если:
- её захватило escaping-замыкание;
- она поле класса или элемент массива (буфер массива в куче);
- она лежит в
any Protocolи не влезает в inline-буфер на 3 машинных слова.
А оптимизатор может разместить объект класса на стеке, если тот не покидает функцию. Поэтому правильный ответ про разницу: семантика копирования, идентичность и подсчёт ссылок, а не стек и куча.
Идентичность и равенство
У объектов класса есть идентичность: оператор === проверяет, что две ссылки указывают на один объект. У значений идентичности нет, только равенство == через Equatable.
Что выбрать
Apple советует начинать со структуры. Класс берут, когда нужно:
- общее изменяемое состояние с идентичностью (кеш, сервис, сессия);
- наследование (например, от
UIViewController); deinitдля освобождения ресурса;- совместимость с Objective-C.
Модели данных, DTO, конфигурации, состояние экрана для SwiftUI обычно структуры. View model чаще класс: на неё ссылаются из нескольких мест, и она живёт дольше одного пересчёта.
Новое в Swift 5.9+: некопируемые типы
struct FileDescriptor: ~Copyable запрещает копирование: у значения один владелец, передача в функцию идёт как borrowing (временный доступ) или consuming (забрать владение), а у такой структуры может быть deinit. Это редкий, но senior-вопрос.
Частые ошибки
- Говорить «struct всегда на стеке».
- Забыть, что
letдля класса не делает объект неизменяемым. - Положить класс в структуру и ожидать независимых копий.
- Ожидать, что изменение элемента массива-копии не затронет исходный, когда элементы это объекты.
Что спросят на собесе
- Чем
structотличается отclass, когда что выбрать? - Что такое copy-on-write и где он есть?
- Что выведет код с копированием структуры, внутри которой класс?
- Где хранится структура и всегда ли на стеке?
- Зачем нужен
mutatingи почему его нет у классов?