> Как устроены слои в чистой архитектуре? (iOS, Swift)
Уровень: senior · Роль: mobile · Категория: Технические вопросы
Компании: Travelata
Стек: iOS, Swift
> Пример ответа
Короткий ответ
Чистая архитектура делит приложение на слои с направлением зависимостей строго внутрь: domain (entities и use cases), data (repositories и их реализации) и presentation (UI и view models). Внутренние слои не знают о внешних - только через протоколы (интерфейсы). Для iOS это означает, что domain не импортирует UIKit, а presentation зависит от абстракций use cases, а не от конкретных сервисов. Главная цель - изолировать бизнес-логику от фреймворков и упростить тестирование.
Подробное объяснение
Слои в чистой архитектуре (по Роберту Мартину) - это концентрические круги: entities, use cases, interface adapters, frameworks and drivers. На iOS обычно выделяют три основных слоя:
- Domain - самый внутренний. Содержит entities (модели бизнес-логики) и use cases (интеракторы). Не зависит ни от чего, кроме стандартной библиотеки Swift. Не импортирует UIKit, Foundation (по возможности), сторонние библиотеки.
- Data - реализация репозиториев, маппинг DTO в domain-модели, работа с сетью, БД, UserDefaults. Зависит от domain (реализует протоколы репозиториев), но не от presentation.
- Presentation - View, ViewModel (или Presenter), координация навигации. Зависит от domain через протоколы use cases. Может использовать фреймворки (UIKit, SwiftUI), но не содержит бизнес-правил.
Ключевой принцип - dependency rule: исходный код зависит только внутрь круга. Внешние слои могут использовать внутренние, но не наоборот. Это достигается через инверсию зависимостей: domain объявляет протоколы (например, UserRepository), а data предоставляет конкретную реализацию (UserRepositoryImpl). Presentation получает эту реализацию через DI-контейнер или инициализатор.
На iOS это часто выглядит так: ViewModel держит ссылку на протокол FetchUsersUseCase, а не на конкретный класс. В рантайме туда подставляется реализация, которая использует URLSession или CoreData - но ViewModel об этом не знает.
На практике
В реальном iOS-проекте слои обычно организуют в виде отдельных папок или Swift packages (например, Domain, Data, Presentation). Навигация между слоями происходит через координаторы или роутеры, которые живут в presentation. DI-контейнер (Swinject, Resolver или ручная сборка) связывает протоколы с реализациями на старте приложения.
Типичный поток: View вызывает метод ViewModel → ViewModel вызывает use case (через протокол) → use case обращается к репозиторию (через протокол) → репозиторий (реализация в data) делает сетевой запрос → возвращает DTO → маппится в domain-модель → use case возвращает результат → ViewModel обновляет state → View рендерит.
На практике часто добавляют слой Common или Core для утилит, но он не должен содержать бизнес-логику. Также важно не путать слои с модулями: слои - это логическое разделение, а модули (packages) - физическое. Можно иметь один модуль с чёткими папками, но лучше - отдельные таргеты для domain и data, чтобы enforce dependency rule на уровне компилятора.
Пример кода
SWIFT// Domainprotocol UserRepository {func fetchUser(id: String) async throws -> User}struct User: Equatable {let id: Stringlet name: String}protocol FetchUserUseCase {func execute(id: String) async throws -> User}final class FetchUserUseCaseImpl: FetchUserUseCase {private let repository: UserRepositoryinit(repository: UserRepository) {self.repository = repository}func execute(id: String) async throws -> User {try await repository.fetchUser(id: id)}}// Datastruct UserDTO: Decodable {let id: Stringlet name: String}final class UserRepositoryImpl: UserRepository {private let apiClient: APIClientinit(apiClient: APIClient) {self.apiClient = apiClient}func fetchUser(id: String) async throws -> User {let dto: UserDTO = try await apiClient.get("/users/\(id)")return User(id: dto.id, name: dto.name)}}// Presentationfinal class UserViewModel: ObservableObject {@Published var user: User?private let useCase: FetchUserUseCaseinit(useCase: FetchUserUseCase) {self.useCase = useCase}func load(id: String) async {user = try? await useCase.execute(id: id)}}
Как отвечать на собеседовании
Начни с определения слоёв и dependency rule. Приведи конкретный пример из iOS: domain не знает про UIKit, data реализует протоколы, presentation зависит от абстракций. Обязательно упомяни инверсию зависимостей и DI. Если спросят про trade-off - скажи, что чистая архитектура добавляет boilerplate и сложность для маленьких проектов, но окупается в больших, где важна тестируемость и поддерживаемость. Покажи, как тестировать use case с мок-репозиторием. Хорошо, если упомянешь, что на практике не всегда нужны все слои - для простого экрана можно обойтись без отдельного use case.
Что проверяет интервьюер
Интервьюер оценивает: понимание направления зависимостей (внутрь), умение объяснить, зачем нужны протоколы между слоями, знание, как это применяется в iOS (UIKit/SwiftUI, DI, тестирование). Важно показать, что ты понимаешь разницу между domain-моделью и DTO, и почему нельзя тянуть CoreData-объекты в presentation. Также проверяется способность рассуждать о границах: что класть в domain, а что в data. Если кандидат путает слои с VIPER или MVC - это красный флаг.
Типичные ошибки
- Путаница между слоями и архитектурными паттернами (VIPER, MVVM) - это не одно и то же.
- Утверждение, что domain может импортировать UIKit - нарушение dependency rule.
- Хранение бизнес-логики в ViewModel или View - это нарушение изоляции.
- Отсутствие протоколов: presentation напрямую зависит от конкретной реализации репозитория.
- Передача DTO или CoreData-объектов в presentation вместо domain-моделей.
- Слишком фанатичное следование: создание use case для каждого экрана даже там, где это не нужно - интервьюер ценит прагматизм.
> Похожие задачи по mobile
Можно ли применять вложенные циклы и как оптимизировать алгоритмы
В чем разница HTTP методов GET, POST, PUT, DELETE и когда их использовать
Как избежать двойного оформления заказа при нестабильном интернете и повторных запросах?
Какие есть варианты кэширования на уровне приложения?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью