Что обычно хранит ViewModel в SwiftUI?
Что проверяют
Понимаете ли вы границу между слоями: что относится к состоянию экрана, а что к самой вьюхе, и как изменения доезжают до интерфейса. Это вопрос про архитектуру, а не про синтаксис.
Как отвечать
ViewModel держит состояние экрана и правила его изменения, но не сам интерфейс:
- данные экрана в готовом для показа виде: отформатированные строки, собранные модели ячеек;
- состояние процесса: ожидание, загрузка, ошибка, пустой результат. Удобно одним перечислением, тогда невозможные сочетания вроде «и индикатор, и ошибка» просто не выражаются;
- пользовательский ввод, если он влияет на запросы: строка поиска, выбранный фильтр;
- зависимости: сервисы и репозитории, переданные снаружи, чтобы их можно было подменить в тестах;
- действия: методы вида
load,refresh, обработчики нажатий.
@MainActor
final class FeedViewModel: ObservableObject {
@Published private(set) var state: State = .idle
private let service: FeedService
init(service: FeedService) { self.service = service }
}Публикация изменений это ObservableObject с @Published, в новых версиях вместо него применяют макрос наблюдения, который снимает лишние перерисовки. Свойства обновляются на главном потоке, поэтому класс помечают @MainActor.
Не хранит: сами вьюхи, цвета и шрифты, контроллеры UIKit, а также синглтоны, добытые внутри вместо передачи в инициализатор.
Где спотыкаются
Путают @State и модель. @State это состояние самой вьюхи: открыт ли лист, где фокус. Внутри ObservableObject эта обёртка не работает, хотя компилятор её пропускает.
Второе: объявляют модель как @ObservedObject var vm = ViewModel(). При перерисовке родителя объект создаётся заново, и экран теряет данные. Владелец объявляет её через @StateObject, а дочерние вьюхи получают готовую через @ObservedObject.
Третье: складывают в модель сетевой код целиком. Тогда её не протестировать без сети, а тот же запрос дублируется на соседнем экране. Запросы живут в сервисе, модель его вызывает и переводит ответ в состояние.