# Stanford CS193p: Как превратить модель SwiftUI в персистентную базу данных

Источник: https://www.youtube.com/watch?v=k9wjAdgUY0A
Канал: Stanford Online
Опубликовано: 18.12.2025

---

В тринадцатой лекции курса Stanford CS193p (весна 2025 года) профессор Пол Хэгарти приступает к одной из важнейших тем современной iOS-разработки — сохранению данных с помощью фреймворка SwiftData. Основная цель занятия — научить студентов делать данные приложения персистентными, чтобы информация не исчезала после закрытия программы или перезагрузки устройства [0:05]. SwiftData представляет собой мощную надстройку над базой данных SQL, которая позволяет разработчикам работать на уровне привычных конструкций Swift, не погружаясь в сложности запросов к БД [0:33].

## 🛠️ Основы SwiftData: от классов к таблицам SQL
[[JUMP:01:15]]

Переход к персистентной модели начинается с использования макроса `@Model`. По словам Хэгарти, этот макрос можно применять только к классам, поскольку именно они будут представлять собой таблицы в базе данных [1:44]. Важной особенностью является то, что `@Model` автоматически делает класс наблюдаемым (Observable), поэтому использовать оба макроса одновременно не имеет смысла [2:10].

Поскольку данные сохраняются в SQL, на типы переменных внутри `@Model` накладываются определенные ограничения:

*   Примитивные типы: числа, строки, булевы значения, даты и буферы данных (Data) [2:55].
*   Другие `@Model`: переменная может ссылаться на другой класс, помеченный этим же макросом, что создает связь между таблицами [3:10].
*   Структуры и перечисления (Enums): они должны соответствовать протоколу `Codable`, чтобы SwiftData могла упаковать их в «бинарный мешок» для хранения [4:16]. Однако профессор предупреждает: такие данные становятся непрозрачными для поиска внутри базы [4:28].
*   Массивы: допускаются массивы любых вышеперечисленных типов, но массивы других `@Model` всегда будут неупорядоченными при извлечении из базы [4:58].

Хэгарти подчеркивает, что если тип данных не вписывается в эти рамки, разработчик может использовать вычисляемые свойства для преобразования данных в примитивные типы «на лету» [6:11].

## 🔗 Управление связями и атрибутами схемы
[[JUMP:07:38]]

Структура базы данных описывается понятием «схема». Для настройки поведения полей используются атрибуты схемы. Например, атрибут `.unique` позволяет гарантировать уникальность значения (как ID сотрудника), а `.externalStorage` полезен для хранения тяжелых объектов, таких как видео или крупные изображения, вне основного файла базы данных [8:09].

Особое внимание Хэгарти уделяет управлению связями через `@Relationship`:

1.  Правила удаления (`deleteRule`): профессор рекомендует использовать `.cascade`, чтобы при удалении основного объекта (например, игры) автоматически удалялись связанные с ним данные (попытки и коды) [9:20].
2.  Инверсия (`inverse`): позволяет SwiftData понимать двусторонние связи, когда дочерний объект имеет ссылку на родительский [9:35].

Для данных, которые нужны только во время работы приложения и не должны сохраняться на диск, предусмотрены маркеры `.ephemeral` или `@Transient` [8:51]. Однако, по наблюдению Хэгарти, изменения `@Transient` переменных могут не обновлять пользовательский интерфейс автоматически, что требует ручной инициализации обновлений [42:42].

## 📑 Модель контекста и контейнера
[[JUMP:10:01]]

Для взаимодействия с базой данных используются два ключевых компонента:

*   `modelContext`: это основной «инструмент» или дескриптор для добавления (`insert`), удаления (`delete`) и извлечения (`fetch`) объектов [11:09]. Он также управляет транзакциями и функциями отмены (undo).
*   `modelContainer`: контролирует физическое хранение данных (на диске или в памяти) и миграцию схем при обновлении версий приложения [12:03].

Хэгарти отмечает, что операции со SwiftData являются синхронными, но если приложение настроено на работу с iCloud, синхронизация с облаком происходит асинхронно в фоновом режиме, избавляя разработчика от необходимости писать сложный многопоточный код [14:31].

## 🔍 Искусство выборки данных: Predicates и @Query
[[JUMP:14:45]]

Извлечение данных происходит с помощью `FetchDescriptor`, который определяет три вещи: тип искомой модели, условия поиска и порядок сортировки [15:00]. Для задания условий используется макрос `#Predicate`. Это мощный инструмент, который превращает обычное замыкание Swift в сложный SQL-запрос [16:08].

Внутри предиктов можно использовать:

*   Математические операции и сравнения.
*   Логические операторы (`&&`, `||`) и тернарные операторы [18:48].
*   Операции с последовательностями (`contains`, `allSatisfy`) и точечную нотацию для переходов между связанными таблицами [19:19].

Несмотря на гибкость, предикты имеют ограничения: в них нельзя использовать вычисляемые свойства, так как база данных «видит» только реально хранящиеся поля [21:37].

В SwiftUI-представлениях профессор рекомендует использовать макрос `@Query` вместо ручного вызова `fetch()`. Это позволяет создать массив, который автоматически обновляется при любых изменениях в базе данных, работая аналогично `@State` или `@Binding` [29:09].

## ⚠️ Обработка ошибок в Swift
[[JUMP:21:52]]

Работа с базой данных — это первый случай в курсе, когда функции могут возвращать ошибки (например, из-за повреждения файлов). Хэгарти объясняет стандартную систему Swift `do-catch`:

*   Функции, способные выдать ошибку, помечаются словом `throws` и должны вызываться с префиксом `try` [22:05].
*   Блок `catch` позволяет перехватить ошибку и обработать её (например, показать уведомление пользователю) [23:04].
*   Существуют также `try!` (вызывает фатальную ошибку при неудаче) и `try?` (возвращает `nil` вместо ошибки, что удобно для простых проверок) [26:34].

## 👨‍💻 Демо: адаптация модели CodeBreaker под SwiftData
[[JUMP:31:30]]

Практическая часть занятия посвящена конвертации существующей игры CodeBreaker. Основная сложность заключается в том, что типы данных в модели не всегда совместимы с SQL. Например, тип `Color` не является примитивным и не может быть сохранен напрямую [34:53].

Хэгарти демонстрирует «массаж данных» (data massaging):

1.  **Превращение структур в классы**: `Code` становится `@Model class`, что требует добавления инициализатора, так как классы не получают его автоматически, в отличие от структур [37:13].
2.  **Проблема ссылок**: Хэгарти указывает на опасный баг — при переходе от структур к классам простое присваивание перестает копировать данные, создавая лишь вторую ссылку на тот же объект в куче [39:17]. Это требует явного создания копий объектов.
3.  **Использование ИИ**: Профессор обращается к ChatGPT, чтобы быстро сгенерировать код для перечисления `Kind`, которое содержит ассоциативные данные и не может быть автоматически преобразовано в строку [50:20]. Хэгарти выбирает хранение этого перечисления в виде `String` в базе данных, чтобы сохранить возможность поиска по типу [46:02].
4.  **UI-независимость**: Хэгарти делает модель полностью независимой от SwiftUI, заменяя `Color` на `String` (Hex-коды). Чтобы не ломать UI-код, он добавляет расширения с вычисляемыми свойствами, которые прозрачно конвертируют строки обратно в цвета для представлений [1:06:57].

В завершение лекции в главный файл приложения добавляется `.modelContainer(for: CodeBreaker.self)` [1:09:28]. Профессор демонстрирует, что после всех манипуляций игра продолжает работать, а данные теперь готовы к полноценному сохранению, которое будет реализовано на следующем занятии [1:10:40].