Мобильная разработка

UIKit и архитектуры iOS

2015 год. Airbnb iOS приложение: 1 миллион строк кода. 80% кода - в UIViewController подклассах. Рефакторинг невозможен - тест coverage 0%, каждое изменение ломает что-то ещё. Команда тратит 6 месяцев на переход к MVVM + Coordinator. После: coverage 70%, feature velocity 3x.

  • **Uber iOS:** VIPER архитектура (View-Interactor-Presenter-Entity-Router) для масштабирования на 200+ разработчиков. Каждый модуль независим и тестируем
  • **Airbnb:** RxSwift MVVM с 2016, переход на SwiftUI + Combine с 2022. Поддерживают backward compatibility с UIKit через UIViewControllerRepresentable
  • **Spotify iOS:** самодельный декларативный UI framework поверх UIKit (2019) до перехода на SwiftUI

Исторический контекст

В 2007 году Apple выпустила iPhone SDK с UIKit и рекомендованным паттерном MVC. UIViewController появился как центральный элемент архитектуры - управляет view иерархией и обрабатывает events. Проблема «Massive View Controller» стала очевидна к 2012-2013 годам по мере роста сложности приложений. В 2015 году Сохар Хонлоу (Kickstarter) опубликовал Coordinator паттерн. RxSwift был портирован с .NET Reactive Extensions в 2014. Combine появился в 2019 (iOS 13) как Apple-officially поддержанная реактивная библиотека. SwiftUI (2019) предложил принципиально иной декларативный подход.

MVC по Apple: Massive View Controller в реальности

**Apple рекомендует MVC (Model-View-Controller) с 2007 года.** Теория: Controller - тонкий посредник между Model и View. Реальность: iOS разработчики прозвали UIViewController «Massive View Controller» - класс по 1000-2000 строк, который управляет UIKit, сетью, парсингом данных, навигацией и бизнес-логикой одновременно. UIViewController нарушает SRP по умолчанию - он обязан реализовывать viewDidLoad, viewWillAppear и десятки других методов UIKit.

**Что реально делает UIViewController в Apple MVC:** управляет жизненным циклом View (загрузка, появление, исчезновение), обрабатывает события rotation/memory, является delegate для UIKit компонентов. Это уже много ответственностей. Бизнес-логика и сетевые запросы добавляют третью и четвёртую. Отсюда - все альтернативные архитектуры.

Почему UIViewController в Apple MVC часто становится «Massive»? Что в UIKit дизайне провоцирует это?

MVVM с Combine: тонкий ViewController

**MVVM (Model-View-ViewModel)** переносит бизнес-логику из ViewController во ViewModel. ViewController становится тонким: только UIKit код (layout, animations, transitions). ViewModel - независимый от UIKit класс: тестируемый, переиспользуемый. Связь: Combine (Apple, iOS 13+) или RxSwift. Data binding превращает ViewModel output в UI updates автоматически.

**Тестирование MVVM:** ViewModel без UIKit зависимостей - идеален для unit tests. Подменяем UserServiceProtocol на MockUserService, вызываем viewDidLoad.send() и проверяем @Published свойства. XCTestExpectation + Combine: `viewModel.$users.first().sink { ... }`. ViewController не тестируем unit-тестами - только UI тесты.

В MVVM ViewModel публикует данные через @Published. Почему данные должны быть в UIKit-независимых типах ([User], не [UITableViewCell])?

Coordinator Pattern: навигация вне ViewController

**Khanlou, 2015. Сооснователь Kickstarter пишет статью 'The Coordinator'.** Проблема: даже с MVVM навигация остаётся в ViewController: `navigationController?.pushViewController(...)` создаёт жёсткую связь между экранами. ViewController знает о следующем ViewController. Coordinator паттерн извлекает навигацию в отдельный класс - ViewController не знает о существовании других экранов.

**Coordinator + Deep Links:** при открытии push notification с deeplink (например `myapp://user/123`) AppCoordinator создаёт нужный дочерний coordinator и вызывает showUserDetail(user) напрямую. Без coordinator каждый VC должен уметь обрабатывать deeplinks - дублирование кода.

UserListViewController содержит код `let detailVC = UserDetailViewController(); navigationController?.push(detailVC)`. Какой принцип нарушен?

Жизненный цикл UIViewController

**Самая частая причина багов в iOS - неправильное понимание жизненного цикла UIViewController.** Данные загружают в `init` вместо `viewDidLoad`. Подписки на уведомления создают в `viewWillAppear` но не удаляют в `viewWillDisappear`. UI обновляют до того как view иерархия создана. Порядок вызовов строго определён UIKit и зависит от контекста (push, present, tab switch).

**Memory leak через retain cycle:** ViewController держит ViewModel, ViewModel держит closure из ViewController без [weak self]. Проверка: добавить `print` в `deinit` ViewController - должен вызываться при pop со стека навигации. Инструмент: Instruments -> Leaks или Debug Memory Graph в Xcode.

viewDidDisappear означает что UIViewController уничтожен и освобождён из памяти

viewDidDisappear означает что view не видна. ViewController может оставаться в памяти долго: navigation stack хранит все VC, tab bar держит дочерние VC постоянно.

UINavigationController сохраняет все VC в стеке пока они не будут pop'нуты. Presentation stack аналогично. ViewDidDisappear вызывается при each navigation - но VC живёт пока на него есть strong reference. Утечка: если VC не освобождается при pop - retain cycle.

viewDidLoad вызван. Можно ли использовать `view.frame.width` для расчёта layout?

UIKit архитектуры: главное

  • MVC по Apple: UIViewController получает слишком много обязанностей через UIKit delegate pattern - Massive View Controller антипаттерн
  • MVVM: бизнес-логика во ViewModel (без UIKit), data binding через Combine/RxSwift, ViewController - только UIKit код
  • Coordinator: навигация вне ViewController, VC не знает о следующих экранах, легко добавлять deep link handling
  • Жизненный цикл: viewDidLoad (один раз, frame нефинален), viewWillAppear (каждый показ), viewDidDisappear (не уничтожение)
  • Retain cycles: VC держит ViewModel -> ViewModel держит closure -> [weak self] обязателен. Проверка через deinit

Вопросы для размышления

  • SwiftUI (2019) предлагает @ObservableObject и @StateObject вместо MVVM с Combine. Нужен ли Coordinator паттерн в SwiftUI приложении или NavigationStack решает ту же проблему?

Связанные уроки

  • mob-03
  • mob-05
  • se-04
  • web-04
  • mob-06
  • comp-01-intro
UIKit и архитектуры iOS

0

1

Войти