> Какие паттерны проектирования вы знаете и использовали в работе (iOS, Swift)

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

Компании: SimbirSoft, MTS, Физтех-Центр, Bip.ru, Ozon, Masterdata, КРЕЙТЕКС

Стек: iOS, Swift

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

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

В работе с iOS и Swift я регулярно использую порождающие, структурные и поведенческие паттерны. Из порождающих - Singleton (для менеджеров состояния), Factory (для создания view-моделей), Builder (для сложной конфигурации запросов). Из структурных - MVC/MVVM как архитектурные, Adapter (для работы с legacy-кодом), Facade (для обертки над сложными системами). Из поведенческих - Observer (через Combine), Coordinator (для навигации), Strategy (для настройки поведения). Важно не просто знать названия, а понимать, когда паттерн решает проблему, а когда создает лишнюю сложность.

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

Паттерны проектирования - это проверенные решения типовых задач, но в iOS-разработке они часто трансформируются под особенности платформы. Например, Singleton в Swift часто критикуют за скрытые зависимости, но он оправдан для действительно глобальных сущностей (например, URLSession.shared). Вместо классического Observer мы используем Combine с @Published и PassthroughSubject - это нативный инструмент, который заменяет ручную реализацию.

Для архитектуры на уровне приложения я чаще применяю MVVM с Coordinator'ом, чем классический MVC, потому что это упрощает тестирование и управление навигацией. Из структурных паттернов полезен Adapter - например, когда нужно, чтобы разные модели данных отображались в одной UITableView через общий протокол. Facade хорошо ложится на обертку над Core Data или Network layer.

Среди поведенческих - Strategy удобен для переключения между разными алгоритмами (например, кэширование или загрузка данных), а Template Method - для базовых классов с переопределяемыми шагами. Iterator неявно используется в Sequence и Collection. Важно помнить: паттерн - это инструмент, а не цель. Иногда простая функция или замыкание решает задачу лучше, чем полноценная иерархия классов.

На практике

В реальных проектах я чаще всего сталкиваюсь с такими сценариями:

  • Coordinator + MVVM: навигация выносится в отдельные классы, view-модели не знают о UIKit. Это упрощает переиспользование и deep linking.
  • Factory: используется для создания view-моделей с разными зависимостями (например, для разных тарифов или ролей пользователя).
  • Adapter: когда нужно отобразить в одном списке объекты разных типов (например, сообщения и системные уведомления) через общий протокол CellConfigurable.
  • Observer через Combine: подписка на изменения состояния (например, корзина или авторизация) без ручного делегирования.
  • Facade: для инкапсуляции работы с несколькими сервисами (аналитика, логирование, сеть) в одном интерфейсе.

Также я использую Builder для конфигурации сложных URLRequest или attributed strings, когда параметров много и они опциональны. Prototype редко, но бывает полезен для копирования сложных объектов (например, черновиков).

Пример кода

Пример Adapter для отображения разных моделей в одном списке:

SWIFT
protocol CellConfigurable {
func configure(cell: UITableViewCell)
}
struct MessageItem: CellConfigurable {
let text: String
func configure(cell: UITableViewCell) {
cell.textLabel?.text = text
}
}
struct SystemNotificationItem: CellConfigurable {
let title: String
let icon: String
func configure(cell: UITableViewCell) {
cell.textLabel?.text = title
cell.imageView?.image = UIImage(systemName: icon)
}
}
// В контроллере:
let items: [CellConfigurable] = [MessageItem(text: "Привет"), SystemNotificationItem(title: "Обновление", icon: "bell")]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
items[indexPath.row].configure(cell: cell)
return cell
}

Пример Strategy для выбора источника данных:

SWIFT
protocol DataLoadingStrategy {
func load(completion: @escaping (Result<[Post], Error>) -> Void)
}
final class NetworkLoadingStrategy: DataLoadingStrategy {
func load(completion: @escaping (Result<[Post], Error>) -> Void) {
// загрузка с сервера
}
}
final class CacheLoadingStrategy: DataLoadingStrategy {
func load(completion: @escaping (Result<[Post], Error>) -> Void) {
// загрузка из кэша
}
}
final class FeedViewModel {
private var strategy: DataLoadingStrategy
init(strategy: DataLoadingStrategy) {
self.strategy = strategy
}
func setStrategy(_ strategy: DataLoadingStrategy) {
self.strategy = strategy
}
func loadPosts() {
strategy.load { result in
// обработка
}
}
}

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

Начните с краткого перечисления основных групп паттернов, затем переходите к конкретным примерам из вашего опыта. Важно показать, что вы понимаете не только механику, но и мотивацию: какую проблему решал паттерн, какие альтернативы рассматривали, какие trade-off приняли. Расскажите про конкретный кейс: например, как Coordinator помог справиться с разрастающейся навигацией, или почему вы заменили Singleton на DI-контейнер. Упомяните, что в Swift многие паттерны реализуются через протоколы и замыкания, а не через классические классы. Если спросят про конкретный паттерн - опишите его структуру, участников и пример использования. Не перечисляйте все паттерны подряд без контекста - это звучит как заучивание.

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

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

  • понимание, зачем нужны паттерны и когда они уместны;
  • способность применить паттерн к реальной задаче из iOS-разработки;
  • знание альтернатив и умение объяснить выбор;
  • понимание ограничений и анти-паттернов (например, злоупотребление Singleton);
  • знание нативных инструментов (Combine, SwiftUI, UIKit), которые заменяют классические реализации.

Также проверяется, насколько глубоко кандидат разбирается в архитектуре приложения в целом, а не просто знает названия.

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

  • Перечисление всех паттернов без примеров и обоснования.
  • Утверждение, что Singleton - это всегда плохо, без нюансов.
  • Путаница между паттернами и архитектурными подходами (MVC, MVVM - это архитектура, а не паттерн).
  • Игнорирование нативных альтернатив: например, использование делегатов там, где лучше подходит Combine.
  • Предложение паттерна ради паттерна, когда задача решается простой функцией.
  • Неумение объяснить, как паттерн влияет на тестируемость и поддерживаемость кода.

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

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