Как организовать слой сохранения данных и модульность в архитектуре MVP
Что проверяют
Где в MVP живёт работа с данными и умеете ли вы спрятать хранилище за протоколом так, чтобы Presenter не знал про Core Data. Следом спрашивают, кто собирает модуль и передаёт зависимости и как тестировать Presenter без базы.
Как отвечать
MVP описывает только экран: пассивная View (UIViewController, реализующий протокол), Presenter с логикой отображения и Model, куда входит всё остальное. Слой данных это часть Model, и его стоит вынести явно:
- репозиторий: протокол вроде
NotesRepositoryс методамиfunc notes() async throws -> [Note]иfunc save(_ note: Note) async throws. Presenter зависит только от протокола; - реализации: Core Data, SwiftData (iOS 17+), SQLite через GRDB,
UserDefaultsдля настроек, Keychain для токенов, файлы для кеша. Репозиторий может совмещать сеть и локальный кеш, Presenter этого не видит; - свои модели: наружу отдаются простые структуры, а не
NSManagedObjectили классы@Model. Иначе детали хранилища утекают в экран, а управляемые объекты ещё и привязаны к своему контексту и потоку; - сборка: фабрика модуля или координатор создаёт View и Presenter и передаёт репозиторий через инициализатор. Синглтон
DataManager.sharedвнутри Presenter убивает тестируемость; - модульность: локальные Swift-пакеты, например
Domainс моделями и протоколами,Persistence,Networkingи отдельные пакеты фич. Фичи зависят отDomain, а не от реализации хранилища, поэтому не полезут в базу в обход репозитория, а инкрементальная сборка ускоряется; - тесты: мок репозитория отдаёт заранее известные данные, тест проверяет, что Presenter вызвал у View нужный метод.
Где спотыкаются
Presenter сам обращается к NSManagedObjectContext или UserDefaults. Протестировать его без настоящего хранилища уже нельзя.
Держат сильные ссылки в обе стороны. View владеет Presenter, значит ссылка Presenter на View должна быть weak, иначе экран не освободится.
Делают загрузку синхронной на главном потоке и пишут крупные изменения через viewContext. На больших объёмах это фризы интерфейса; тяжёлую работу переносят в фоновый контекст.