Урок по треку Swift и iOS 7 мин

Типы значений и ссылочные типы в Swift

struct, class и enum, семантика копирования, copy-on-write, mutating, identity и равенство.

С этого вопроса начинается почти каждый 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 и почему его нет у классов?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.

Вопросы модуля (24)