> Какую архитектуру вы бы выбрали для годового проекта (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Совкомбанк

Стек: iOS, Swift

> Пример ответа

Короткий ответ

Для годового iOS-проекта я бы выбрал модульную архитектуру на основе MVVM + Coordinator, с чётким разделением на слои (Presentation, Domain, Data) и внедрением зависимостей через constructor injection. Такой подход обеспечивает масштабируемость, тестируемость и поддерживаемость на длинной дистанции, а также упрощает параллельную работу нескольких разработчиков. В качестве дополнительных инструментов - Swift Package Manager для модулей и Combine или async/await для реактивных связей.

Подробное объяснение

Годовой проект - это долгосрочная инвестиция, поэтому архитектура должна решать три задачи: устойчивость к изменениям требований, удобство рефакторинга и возможность расширения команды. MVVM выбран не случайно: он даёт чёткое разделение ответственности между view, view model и моделью, а Coordinator выносит навигацию из контроллеров, что критично для больших экранов и сложных flow.

Слоистая структура выглядит так:

  • Presentation layer: ViewControllers, Views, ViewModels, Coordinators. Здесь нет бизнес-логики, только состояние экрана и пользовательские действия.
  • Domain layer: use cases, entity, интерфейсы репозиториев. Чистая бизнес-логика, не зависит от UIKit.
  • Data layer: реализации репозиториев, сетевые сервисы, локальное хранилище (CoreData, Realm, или просто файлы).

Модульность через SPM позволяет изолировать фичи (например, авторизация, профиль, каталог) в отдельные пакеты. Это даёт возможность переиспользовать код, ускорять сборку и избегать циклических зависимостей. Для реактивного взаимодействия между слоями я бы использовал async/await - он проще для чтения, чем Combine, и хорошо ложится на современный Swift.

Важный trade-off: не стоит делать архитектуру избыточной. Если проект небольшой и команда из 2-3 человек, полная модульность может замедлить разработку. Но для годового проекта с вероятным ростом команды - это оправдано.

На практике

Начинаю с определения границ модулей: выделяю core-модуль (сеть, DI, базовые утилиты) и feature-модули. Каждый feature-модуль содержит свой MVVM + Coordinator и не зависит от других фич. Связь между модулями - через протоколы, определённые в core.

Для DI использую простой контейнер (например, Resolver или ручную фабрику), но без сторонних библиотек, если нет необходимости. Constructor injection - обязателен, чтобы каждый объект можно было легко заменить на mock в тестах.

Навигация через Coordinator: каждый экран имеет свой coordinator, который отвечает за переходы. Это позволяет переиспользовать экраны в разных контекстах и упрощает deep linking.

Тестируемость: ViewModel не зависит от UIKit, поэтому юнит-тесты покрывают всю бизнес-логику. UI-тесты - только для критических flow.

Пример кода

SWIFT
// Domain layer
protocol UserRepository {
func fetchUser(id: String) async throws -> User
}
// Data layer
final class UserRepositoryImpl: UserRepository {
private let apiClient: APIClient
init(apiClient: APIClient) {
self.apiClient = apiClient
}
func fetchUser(id: String) async throws -> User {
try await apiClient.get("/users/\(id)")
}
}
// Presentation layer
final class UserViewModel {
private let repository: UserRepository
private let coordinator: UserCoordinator
@Published var state: UserState = .loading
init(repository: UserRepository, coordinator: UserCoordinator) {
self.repository = repository
self.coordinator = coordinator
}
func loadUser(id: String) async {
do {
let user = try await repository.fetchUser(id: id)
state = .loaded(user)
} catch {
state = .error(error.localizedDescription)
}
}
func showDetails() {
coordinator.showUserDetails()
}
}
// Coordinator
final class UserCoordinator {
private let navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func showUserDetails() {
let vc = UserDetailsViewController()
navigationController.pushViewController(vc, animated: true)
}
}

Как отвечать на собеседовании

Начните с краткого ответа, затем раскройте мотивацию: почему именно MVVM + Coordinator, а не VIPER или TCA. Обоснуйте выбор слоёв и модульности с точки зрения долгосрочной поддержки. Упомяните trade-off: избыточность архитектуры для маленьких проектов. Покажите, что вы думаете о тестируемости и командной работе. Если спросят про альтернативы - честно скажите, что VIPER даёт больше разделения, но требует больше boilerplate, а TCA - мощный, но сложный в освоении и может замедлить команду.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание trade-off между архитектурными паттернами, а не заучивание одного.
  • Умение проектировать систему с учётом масштабирования и изменений требований.
  • Знание современных инструментов (SPM, async/await, Combine).
  • Способность объяснить, как архитектура влияет на тестируемость и командную работу.
  • Практический опыт: как вы решали проблемы навигации, DI, модульности в реальных проектах.

Типичные ошибки

  • Выбор архитектуры без обоснования: "я всегда использую MVVM" - без объяснения, почему.
  • Игнорирование слоя Domain: вся логика в ViewModel, что приводит к раздуванию и сложности тестирования.
  • Отсутствие Coordinator: навигация размазана по контроллерам, что усложняет deep linking и переиспользование.
  • Чрезмерная модульность: 50 модулей для проекта на 10 экранов - это оверинжиниринг.
  • Забывают про DI: жёсткие зависимости внутри классов, что делает юнит-тесты невозможными без моков.
  • Не учитывают реактивность: если проект требует частого обновления UI, нужно заранее продумать связь между слоями (async/await или Combine).

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью