Паттерны проектирования в Swift: продвинутые техники и примеры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Продвинутые паттерны проектирования в Swift: от теории к практике

Паттерны проектирования — это не просто абстрактные концепции, а проверенные временем решения типовых задач. Если вы уже освоили базовые паттерны (Singleton, Delegate, MVC) и работаете над сложными iOS/macOS приложениями, пора переходить на следующий уровень. В этой статье мы разберём четыре продвинутых паттерна: Builder, Strategy, Observer (через Combine) и Coordinator. Каждый паттерн будет проиллюстрирован реальным кодом на Swift и советами по интеграции в существующие проекты.

1. Builder (Строитель) — гибкая настройка сложных объектов

Паттерн Builder идеально подходит для создания объектов с множеством опциональных параметров. Вместо длинных инициализаторов с десятками параметров вы используете пошаговое построение.

Пример: настройка UIAlertController

class AlertBuilder {
    private var title: String?
    private var message: String?
    private var actions: [UIAlertAction] = []
    private var preferredStyle: UIAlertController.Style = .alert

    func setTitle(_ title: String) -> AlertBuilder {
        self.title = title
        return self
    }

    func setMessage(_ message: String) -> AlertBuilder {
        self.message = message
        return self
    }

    func addAction(title: String, style: UIAlertAction.Style = .default, handler: ((UIAlertAction) -> Void)? = nil) -> AlertBuilder {
        let action = UIAlertAction(title: title, style: style, handler: handler)
        actions.append(action)
        return self
    }

    func setStyle(_ style: UIAlertController.Style) -> AlertBuilder {
        self.preferredStyle = style
        return self
    }

    func build() -> UIAlertController {
        let alert = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
        actions.forEach { alert.addAction($0) }
        return alert
    }
}

// Использование
let alert = AlertBuilder()
    .setTitle("Ошибка")
    .setMessage("Не удалось загрузить данные")
    .addAction(title: "OK", style: .default)
    .addAction(title: "Повторить", style: .cancel)
    .build()
present(alert, animated: true)

Когда использовать: при создании конфигураторов, сложных ViewController или моделей данных с большим количеством опций.

2. Strategy (Стратегия) — переключение алгоритмов на лету

Паттерн Strategy позволяет выделить семейство алгоритмов и делать их взаимозаменяемыми. Это особенно полезно для обработки платежей, валидации данных или рендеринга.

Пример: система фильтрации контента

protocol FilterStrategy {
    func apply(to items: [String]) -> [String]
}

class AlphabeticalFilter: FilterStrategy {
    func apply(to items: [String]) -> [String] {
        return items.sorted()
    }
}

class LengthFilter: FilterStrategy {
    func apply(to items: [String]) -> [String] {
        return items.filter { $0.count > 5 }
    }
}

class ContentManager {
    private var strategy: FilterStrategy

    init(strategy: FilterStrategy) {
        self.strategy = strategy
    }

    func setStrategy(_ strategy: FilterStrategy) {
        self.strategy = strategy
    }

    func filter(items: [String]) -> [String] {
        return strategy.apply(to: items)
    }
}

// Использование
let manager = ContentManager(strategy: AlphabeticalFilter())
let data = ["Swift", "Objective-C", "Kotlin", "Java", "Python"]
print(manager.filter(items: data)) // ["Java", "Kotlin", "Objective-C", "Python", "Swift"]

manager.setStrategy(LengthFilter())
print(manager.filter(items: data)) // ["Objective-C", "Python"]

Совет: комбинируйте Strategy с фабричным методом для выбора стратегии на основе настроек пользователя.

3. Observer (Наблюдатель) через Combine — реактивное программирование

В современном Swift паттерн Observer реализуется через фреймворк Combine. Это мощный инструмент для обработки событий и потоков данных без ручного управления уведомлениями.

Пример: мониторинг состояния сети

import Combine
import Network

class NetworkMonitor: ObservableObject {
    @Published var isConnected: Bool = true
    private let monitor = NWPathMonitor()
    private var cancellables = Set<AnyCancellable>()

    init() {
        monitor.pathUpdateHandler = { [weak self] path in
            DispatchQueue.main.async {
                self?.isConnected = (path.status == .satisfied)
            }
        }
        monitor.start(queue: DispatchQueue.global())
    }

    deinit {
        monitor.cancel()
    }
}

// ViewModel, подписывающаяся на изменения
class MainViewModel: ObservableObject {
    @Published var connectionStatus: String = "Unknown"
    private var cancellables = Set<AnyCancellable>()

    init(monitor: NetworkMonitor) {
        monitor.$isConnected
            .map { $0 ? "Connected" : "Disconnected" }
            .assign(to: &$connectionStatus)
    }
}

Преимущество: Combine автоматически отписывается от издателей при освобождении памяти, предотвращая утечки.

4. Coordinator (Координатор) — управление навигацией

Паттерн Coordinator решает проблему раздутых ViewController, отвечающих за переходы. Координатор берёт на себя всю логику навигации, оставляя VC только отображение данных.

Пример: простой координатор для iOS

protocol Coordinator: AnyObject {
    var childCoordinators: [Coordinator] { get set }
    func start()
}

class MainCoordinator: Coordinator {
    var childCoordinators = [Coordinator]()
    private let navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func start() {
        let vc = MainViewController()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: false)
    }

    func showDetail(with id: String) {
        let detailCoordinator = DetailCoordinator(navigationController: navigationController, itemID: id)
        childCoordinators.append(detailCoordinator)
        detailCoordinator.start()
    }
}

class DetailCoordinator: Coordinator {
    var childCoordinators = [Coordinator]()
    private let navigationController: UINavigationController
    private let itemID: String

    init(navigationController: UINavigationController, itemID: String) {
        self.navigationController = navigationController
        self.itemID = itemID
    }

    func start() {
        let vc = DetailViewController(itemID: itemID)
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: true)
    }
}

// Использование в AppDelegate/SceneDelegate
let nav = UINavigationController()
let coordinator = MainCoordinator(navigationController: nav)
coordinator.start()

Важно: не забывайте удалять завершённые дочерние координаторы из массива childCoordinators, чтобы избежать циклических ссылок.

Заключение

Продвинутые паттерны проектирования — это не

Блоги

Рекомендаций по книгам