Архитектура
MVC, MVVM, VIPER/Clean и однонаправленный поток; паттерны Coordinator и Repository, внедрение зависимостей, SOLID и разбиение приложения на пакеты.
14 вопросов
JuniorТеорияОчень частоЧто в архитектуре MVC от Apple относится к view controller и почему он разрастается до огромного?
Что в архитектуре MVC от Apple относится к view controller и почему он разрастается до огромного?
Controller — посредник между моделью и view: владеет жизненным циклом view, связывает outlet'ы и действия, готовит данные к показу. Он разрастается, потому что MVC не даёт места сети, persistence и data source, и код оседает в нём.
Типичные ошибки
- ✗Думают, что MVC задаёт отдельный слой для сети или persistence
- ✗Кладут бизнес-логику модели в контроллер вместо модели
- ✗Винят storyboard, а не бесхозный код, в разрастании
Уточняющие вопросы
- →Куда вынести data source и сетевой код, чтобы облегчить контроллер?
- →Как архитектура
MVVMменяет зону ответственности view controller?
JuniorТеорияОчень частоЗа что отвечает view model в MVVM, что она никогда не должна импортировать и как view к ней привязывается?
За что отвечает view model в MVVM, что она никогда не должна импортировать и как view к ней привязывается?
View model держит presentation-состояние и логику, готовя данные модели к показу. Она никогда не импортирует UIKit или SwiftUI, оставаясь независимой от фреймворка и тестируемой. View привязывается к ней и обновляется при изменении состояния.
Типичные ошибки
- ✗Позволяют view model импортировать UIKit или SwiftUI и теряют тестируемость
- ✗Кладут presentation-трансформацию в controller вместо view model
- ✗Заставляют view читать view model вручную вместо наблюдения
Уточняющие вопросы
- →Почему отсутствие UIKit во view model делает её пригодной для unit-тестов?
- →Чем view model отличается от controller в классической MVC?
JuniorТеорияЧастоВнедрение зависимостей — через init, свойство или контейнер: почему именно DI делает код тестируемым?
Внедрение зависимостей — через init, свойство или контейнер: почему именно DI делает код тестируемым?
Объект получает свои зависимости извне, а не создаёт их сам. Constructor injection передаёт их в init, property injection задаёт позже, а контейнер разрешает их централизованно. DI делает код тестируемым, ведь в тесте можно подставить mock'и вместо настоящих зависимостей.
Типичные ошибки
- ✗Отождествляют DI с глобальным singleton или service locator
- ✗Думают, что нужен DI-контейнер, а не обычный constructor injection
- ✗Считают, что DI про производительность, а не про заменяемость
Уточняющие вопросы
- →Когда предпочесть property injection вместо constructor injection?
- →Какие минусы у разрешения зависимостей через контейнер?
JuniorТеорияЧастоDelegate, closure, NotificationCenter, реактивный Combine/async — какой стиль общения объектов подходит для какой ситуации?
Delegate, closure, NotificationCenter, реактивный Combine/async — какой стиль общения объектов подходит для какой ситуации?
Delegate — для связи один-к-одному, когда объект отчитывается одному владельцу. Closure — для короткого локального одноразового колбэка. NotificationCenter — для широковещания один-ко-многим неизвестным слушателям. Combine или async — для потоков значений и асинхронной работы.
Типичные ошибки
- ✗Берут NotificationCenter для связи один-к-одному, где delegate яснее
- ✗Тянутся к Combine на одиночный колбэк, где хватило бы closure
- ✗Считают delegate и широковещательное уведомление взаимозаменяемыми
Уточняющие вопросы
- →Почему
NotificationCenterзатрудняет отслеживание потока данных против delegate? - →Когда выбрать async/await вместо колбэка через delegate?
JuniorТеорияЧастоЧто такое паттерн observer, где iOS уже его использует и чем он отличается от delegate?
Что такое паттерн observer, где iOS уже его использует и чем он отличается от delegate?
Паттерн observer позволяет одному subject уведомлять множество подписанных наблюдателей при изменении состояния, не зная, кто они. iOS применяет его в NotificationCenter, KVO и Combine. Delegate отличается тем, что связь один-к-одному — один названный колбэк, а не рассылка.
Типичные ошибки
- ✗Путают широковещание один-ко-многим у observer со связью один-к-одному у delegate
- ✗Думают, что наблюдатели обязаны наследовать subject, за которым следят
- ✗Считают, что NotificationCenter не является реализацией паттерна observer
Уточняющие вопросы
- →Почему широковещательный observer затрудняет отслеживание потока данных против delegate?
- →Когда выбрать delegate вместо
NotificationCenterдля колбэков?
MiddleДизайнЧастоВаши view controller напрямую делают push и present друг друга, поэтому логика навигации разбросана, а экраны нельзя переиспользовать или открыть по диплинку. Вы вводите навигационный паттерн Coordinator, чтобы он владел потоком. Объясните, что координатор забирает у view controller'ов, как дочерний экран возвращает результат своему координатору и как входящий диплинк запускает тот же поток, что и UI.
Ваши view controller напрямую делают push и present друг друга, поэтому логика навигации разбросана, а экраны нельзя переиспользовать или открыть по диплинку. Вы вводите навигационный паттерн Coordinator, чтобы он владел потоком. Объясните, что координатор забирает у view controller'ов, как дочерний экран возвращает результат своему координатору и как входящий диплинк запускает тот же поток, что и UI.
Координатор владеет навигацией — создаёт экраны, внедряет зависимости и решает, что дальше, поэтому view controller'ы больше не знают друг друга. Дочерний экран возвращает результат через delegate или closure, а не делает push сам. Диплинк вызывает те же методы координатора, запуская тот же поток.
Типичные ошибки
- ✗Кладут бизнес-логику в координатор вместо навигации
- ✗Заставляют дочерние экраны навигировать самих, а не возвращать результат
- ✗Обрабатывают диплинки вне координатора, дублируя логику потока
Уточняющие вопросы
- →Почему возвращать результат дочернего через delegate, а не прямым push?
- →Как координатор делает один экран переиспользуемым в разных потоках?
MiddleДизайнЧастоВам достаётся view controller на 1500 строк, где в одном файле смешаны сеть, table data source, валидация ввода и навигация. Команда должна выпускать фичи еженедельно, поэтому полный переписывание исключён, а тестов пока нет. Опишите, как разбить контроллер постепенно — что вынести первым, куда уходит вынесенный код и как оставлять приложение готовым к релизу после каждого шага.
Вам достаётся view controller на 1500 строк, где в одном файле смешаны сеть, table data source, валидация ввода и навигация. Команда должна выпускать фичи еженедельно, поэтому полный переписывание исключён, а тестов пока нет. Опишите, как разбить контроллер постепенно — что вынести первым, куда уходит вынесенный код и как оставлять приложение готовым к релизу после каждого шага.
Выносить безопасными срезами, не переписывать. Сначала вынести data source и сеть в отдельные типы — самые крупные и наименее рисковые. По ходу добавлять характеризующие тесты, направлять контроллер на новые типы и релизить после каждого шага. View model — в последнюю очередь.
Типичные ошибки
- ✗Пытаются переписать всё разом вместо постепенного вынесения
- ✗Выносят UI раньше более ценного кода сети и data source
- ✗Рефакторят непокрытый код, не добавив сперва характеризующих тестов
Уточняющие вопросы
- →Почему вынести data source раньше, чем вводить полноценную view model?
- →Как характеризующие тесты позволяют рефакторить код без спецификаций?
JuniorТеорияИногдаКакие паттерны Gang-of-Four (GoF) есть в iOS — по примеру на singleton, factory, adapter, facade, decorator?
Какие паттерны Gang-of-Four (GoF) есть в iOS — по примеру на singleton, factory, adapter, facade, decorator?
GoF-паттерны описывают повторяющиеся конструкции объектов. В iOS singleton — это URLSession.shared, factory — фабрика вроде UIButton(type:), adapter оборачивает чужой API за протоколом, facade — один вход в подсистему, decorator добавляет поведение обёрткой.
Типичные ошибки
- ✗Думают, что паттерны — это ключевые слова языка, а не структуры объектов
- ✗Считают, что каждый паттерн требует наследования, а не композиции
- ✗Относятся к GoF-паттернам как к чисто Objective-C или устаревшим в Swift
Уточняющие вопросы
- →Почему
URLSession.shared— этоsingleton, а не глобальная функция? - →Когда
adapterлучше, чем изменить чужой тип напрямую?
MiddleТеорияИногдаПоведенческий паттерн observer на практике — ловушки владения и потоков NotificationCenter, KVO, Combine?
Поведенческий паттерн observer на практике — ловушки владения и потоков NotificationCenter, KVO, Combine?
NotificationCenter рассылает синхронно на потоке отправителя, поэтому UI-наблюдатели должны перейти на main, а блочные наблюдатели текут, если их не удалить. KVO требует @objc dynamic key path и может упасть при повторном снятии. Combine отменяется через хранимый AnyCancellable.
Типичные ошибки
- ✗Считают, что уведомление доставляется на main-потоке по умолчанию
- ✗Забывают удалить блочного наблюдателя NotificationCenter
- ✗Думают, что KVO работает без @objc dynamic на наблюдаемом key path
Уточняющие вопросы
- →Почему блочный наблюдатель
NotificationCenterтечёт без явного удаления? - →Почему key path для
KVOдолжен быть объявлен@objc dynamic?
MiddleДизайнИногдаГлобальный singleton AnalyticsManager.shared вызывается напрямую из 40 файлов по всему приложению. Это делает эти типы невозможными для изолированного unit-теста и скрывает их настоящие зависимости. Изменить все 40 мест вызова в одном pull request нельзя. Опишите, как постепенно перейти к внедряемым зависимостям — что вводится первым и как старый и новый код продолжают работать бок о бок во время перехода.
Глобальный singleton AnalyticsManager.shared вызывается напрямую из 40 файлов по всему приложению. Это делает эти типы невозможными для изолированного unit-теста и скрывает их настоящие зависимости. Изменить все 40 мест вызова в одном pull request нельзя. Опишите, как постепенно перейти к внедряемым зависимостям — что вводится первым и как старый и новый код продолжают работать бок о бок во время перехода.
Задать протокол для singleton и внедрять его через init. Мигрировать по одному типу, передавая shared-экземпляр в месте вызова, чтобы поведение не менялось. Держать .shared как аргумент по умолчанию; тесты подставляют fake, а .shared убрать, когда последний вызывающий переедет.
Типичные ошибки
- ✗Пытаются убрать singleton одним махом за один заход
- ✗Меняют singleton на service locator и называют это DI
- ✗Считают, что наследование singleton в тестах снимает связанность
Уточняющие вопросы
- →Почему хранение
.sharedкак аргумента по умолчанию облегчает переход? - →Как внедрение протокола делает зависимость видимой в init?
MiddleДизайнИногдаКласс ImageDownloader качает байты по сети, кэширует их на диск, декодирует в UIImage и рисует их во view — всё в одном типе. Какие из SOLID design principles нарушает такая конструкция и как выглядит более чистая? Назовите обязанности, которые вы бы разделили, и как они зависели бы друг от друга.
Класс ImageDownloader качает байты по сети, кэширует их на диск, декодирует в UIImage и рисует их во view — всё в одном типе. Какие из SOLID design principles нарушает такая конструкция и как выглядит более чистая? Назовите обязанности, которые вы бы разделили, и как они зависели бы друг от друга.
Она нарушает single-responsibility — четыре задачи в одном типе — и вместе с ним open/closed и dependency-inversion, ведь ничто не меняется отдельно. Разбить на fetcher, cache, decoder и view, каждый зависит от следующего через маленький протокол, так что любой можно заменить или протестировать отдельно.
Типичные ошибки
- ✗Видят в этом лишь проблему производительности, а не проектирования
- ✗Называют не тот принцип SOLID для перегрузки обязанностями
- ✗Разделяют обязанности, но связывают их с конкретными типами, а не протоколами
Уточняющие вопросы
- →Почему один тип с четырьмя задачами нарушает и принцип open/closed?
- →Как зависимость от протокола позволяет подменить cache в тесте?
SeniorДизайнИногдаВы выбираете архитектуру представления, на которой команда из 30 инженеров будет строить приложение годами. Кандидаты — MVC от Apple, MVVM, VIPER и однонаправленные подходы вроде Clean или The Composable Architecture (TCA). Выберите один и защитите его против других по тестируемости, стоимости входа, объёму boilerplate и масштабируемости на много команд, работающих параллельно. Назовите компромисс, на который сознательно идёте.
Вы выбираете архитектуру представления, на которой команда из 30 инженеров будет строить приложение годами. Кандидаты — MVC от Apple, MVVM, VIPER и однонаправленные подходы вроде Clean или The Composable Architecture (TCA). Выберите один и защитите его против других по тестируемости, стоимости входа, объёму boilerplate и масштабируемости на много команд, работающих параллельно. Назовите компромисс, на который сознательно идёте.
Для большой команды выбрать MVVM с координаторами: логика представления тестируема, инженеры её знают, boilerplate умеренный. MVC плохо масштабируется на команды; VIPER и TCA сильнее развязаны, но стоят входа и boilerplate. Компромисс — меньше структуры, чем у VIPER/TCA, ради скорости и привычности.
Типичные ошибки
- ✗Заявляют, что MVC самый тестируемый, ведь в нём меньше всего кода
- ✗Считают, что больше слоёв (VIPER) строго лучше независимо от цены
- ✗Игнорируют стоимость входа и масштаб на параллельные команды при выборе
Уточняющие вопросы
- →Почему
MVCмасштабируется хужеMVVM, когда параллельно работает много команд? - →Какую стоимость входа
VIPERиTCAдобавляют противMVVM?
SeniorДизайнИногдаВы структурируете приложение слоистым подходом Clean Architecture, чтобы бизнес-правила оставались независимы от UIKit, сети и базы данных. Где проходят границы слоёв, куда направлены зависимости между ними и что конкретно домену разрешено импортировать? Объясните, как домен обращается к сети или базе, не завися ни от той, ни от другой, используя идею repository.
Вы структурируете приложение слоистым подходом Clean Architecture, чтобы бизнес-правила оставались независимы от UIKit, сети и базы данных. Где проходят границы слоёв, куда направлены зависимости между ними и что конкретно домену разрешено импортировать? Объясните, как домен обращается к сети или базе, не завися ни от той, ни от другой, используя идею repository.
Слои идут domain, presentation, data. Зависимости направлены только внутрь — внешний знает внутренний, не наоборот. Домен не импортирует фреймворкоспецифичного: ни UIKit, ни URLSession, ни Core Data. Он объявляет протоколы repository, которые реализует слой data.
Типичные ошибки
- ✗Позволяют слою domain импортировать UIKit, URLSession или Core Data
- ✗Направляют зависимости наружу, а не внутрь между слоями
- ✗Пропускают протоколы repository и зовут конкретику data из домена
Уточняющие вопросы
- →Почему протокол repository объявляет домен, а не слой data?
- →Как поток зависимостей только внутрь держит бизнес-правила независимыми от фреймворка?
SeniorДизайнИногдаПриложение с одним таргетом разрослось до сотен файлов, и команда хочет разбить его на модули-фичи (Swift-пакеты), чтобы ускорить сборку и закрепить владение. Где провести границы модулей, на какой общий слой опираются фичи и что технически мешает фиче A импортировать фичу B напрямую? Объясните, как одна фича отдаёт то, что нужно другой, без прямой зависимости от неё.
Приложение с одним таргетом разрослось до сотен файлов, и команда хочет разбить его на модули-фичи (Swift-пакеты), чтобы ускорить сборку и закрепить владение. Где провести границы модулей, на какой общий слой опираются фичи и что технически мешает фиче A импортировать фичу B напрямую? Объясните, как одна фича отдаёт то, что нужно другой, без прямой зависимости от неё.
Границы идут по фичам, а под ними — общие модули foundation и domain. Фичи зависят только вниз, никогда вбок друг на друга — граф пакетов превращает боковой import в ошибку компиляции. Когда A нужна B, она использует протокол в общем модуле, который реализует B, связанный на этапе композиции.
Типичные ошибки
- ✗Думают, что папки или группы дают ту же изоляцию, что отдельные модули
- ✗Позволяют фичам зависеть вбок, а не вниз на общие модули
- ✗Связывают фичи с конкретными типами друг друга вместо протоколов
Уточняющие вопросы
- →Почему граф пакетов превращает запрещённый импорт в ошибку компиляции?
- →Как общий протокол позволяет фиче A использовать B, не импортируя её?