# Stanford CS193p: внедрение SwiftData и управление данными в iOS

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

---

## Демонстрация SwiftData: сохранение и управление данными в iOS
[[JUMP:0:05]]

В этой лекции курса Stanford CS193p преподаватель подробно разбирает процесс интеграции SwiftData в существующее приложение CodeBreaker. Основной упор сделан на переход от хранения данных в оперативной памяти (с использованием структуры `struct` и семантики значений) к постоянному хранилищу на основе SQL, а также на эффективное управление запросами, сортировкой и поиском.

### 🛠 Перевод модели на SwiftData
[[JUMP:1:00]]

Для реализации полноценного хранилища модель приложения была существенно модифицирована:

*   **@Model вместо struct:** Классы `Code` и `CodeBreaker` теперь помечены атрибутом `@Model`, что превращает их в сущности базы данных.
*   **Примитивные типы:** Перечисления (enum), такие как `Kind`, были заменены на `String`, так как только примитивные типы могут храниться в базе напрямую. Для удобства работы добавлены вычисляемые свойства для конвертации обратно в нужные типы.
*   **UI-независимость:** Модель избавлена от импорта SwiftUI и типов `Color`. Теперь конвертация цветов в hex-строки происходит на уровне UI (в представлениях или через расширения), что делает ядро приложения независимым от графического интерфейса.
*   **@Relationships и @Transient:** Установлены связи между `CodeBreaker` и `Code` с удалением каскадом (при удалении игры удаляются все её коды). Свойство `startTime` помечено как `@Transient`, так как его хранение в БД не имеет смысла; для корректного обновления UI при изменении `startTime` преподаватель использует «хак» с принудительным обновлением `elapsedTime`.

### 🖼 Изоляция Preview от БД
[[JUMP:9:07]]

Стандартные инструменты Xcode для предварительного просмотра (`#Preview`) не имеют доступа к контейнеру базы данных, что приводило к сбоям. Преподаватель представил решение через систему **Preview Trait**:

1.  Создается `PreviewModifier` под названием `SwiftDataPreview`.
2.  Модификатор использует `ModelContainer` с конфигурацией `isStoredInMemoryOnly: true`, что идеально подходит для тестов, так как не засоряет диск.
3.  Для удобства использования создано расширение `PreviewTrait`, позволяющее вызывать контейнер одной строкой: `#Preview(traits: .swiftData)`.

### 🔍 Запросы, фильтрация и ModelContext
[[JUMP:21:36]]

Переход на БД изменил источник правды: вместо локального массива `[CodeBreaker]` теперь используется `@Query`.

*   **@Query:** Этот атрибут позволяет автоматически получать данные из БД. Он поддерживает фильтрацию через `#Predicate` и сортировку с помощью `SortDescriptor`. Запросы являются «живыми» и обновляются при любых изменениях в базе.
*   **ModelContext:** Это «хаб» для всех операций с данными. Он используется для вызова методов `insert()`, `delete()` и `save()`.
*   **Оптимизация:** Хотя `@Query` закрывает 90% задач, для специальных случаев (например, проверки на пустоту БД) можно использовать `modelContext.fetchCount()`, который эффективнее обычного `fetch()`, так как возвращает только число, не загружая объекты в память.

### 📅 Сортировка и управление состоянием
[[JUMP:45:44]]

Для решения проблемы случайного порядка записей в базе данных была добавлена отметка времени `timestamp` для каждого `Code`. 

*   **Сортировка:** В `CodeBreaker` добавлен вычисляемый массив `attempts` с методом `.sorted()`, который обеспечивает постоянный порядок отображения в UI вне зависимости от порядка хранения в БД.
*   **Динамическая настройка запросов:** Поскольку параметры `@Query` (сортировка, фильтры) нельзя менять «на лету» в теле View, преподаватель демонстрирует технику инициализации запроса прямо в `init()` представления `GameList`. Это позволяет гибко менять логику отображения, например, при переключении пикера «Имя / Недавнее».
*   **Поиск:** Использование модификатора `.searchable(text: $searchString)` в сочетании с динамическим обновлением предиката в `init()` позволяет реализовать полнотекстовый поиск по именам игр.