Паттерны проектирования в Swift: продвинутый уровень с примерами для iOS/macOS
Паттерны проектирования — это проверенные решения типовых задач в разработке. Для Swift-разработчиков, работающих над iOS и macOS приложениями, владение продвинутыми паттернами критически важно для создания масштабируемых, тестируемых и поддерживаемых проектов. В этой статье мы разберем четыре ключевых паттерна: MVVM, Coordinator, Builder и Factory. Каждый из них решает конкретные архитектурные проблемы и активно используется в коммерческой разработке.
1. Паттерн MVVM (Model-View-ViewModel) в Swift
MVVM — один из самых популярных архитектурных паттернов для SwiftUI и UIKit. Он обеспечивает четкое разделение логики представления и бизнес-логики, что упрощает тестирование и переиспользование кода.
Компоненты MVVM:
- Model — данные и бизнес-логика (сетевые запросы, работа с БД).
- View — UI (SwiftUI View или UIViewController).
- ViewModel — посредник, подготавливающий данные для View и обрабатывающий действия пользователя.
Пример реализации на SwiftUI:
import SwiftUI
import Combine
// MARK: - Model
struct User: Codable {
let id: Int
let name: String
let email: String
}
// MARK: - ViewModel
class UserViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
private var cancellables = Set<AnyCancellable>()
func fetchUsers() {
isLoading = true
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: [User].self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { [weak self] completion in
self?.isLoading = false
if case .failure(let error) = completion {
print("Error: \(error)")
}
}, receiveValue: { [weak self] users in
self?.users = users
})
.store(in: &cancellables)
}
}
// MARK: - View
struct UserListView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
NavigationView {
List(viewModel.users, id: \.id) { user in
VStack(alignment: .leading) {
Text(user.name).font(.headline)
Text(user.email).font(.subheadline).foregroundColor(.secondary)
}
}
.navigationTitle("Users")
.overlay {
if viewModel.isLoading {
ProgressView()
}
}
}
.onAppear {
viewModel.fetchUsers()
}
}
}
Преимущества MVVM:
- Чистое разделение ответственности.
- Легкое тестирование ViewModel без UI.
- Реактивное обновление UI через Combine или @Published.
2. Паттерн Coordinator (Координатор) для навигации
Coordinator решает проблему "толстых" ViewController'ов, вынося логику навигации в отдельные объекты. Это упрощает переиспользование экранов и делает навигацию гибкой.
Структура:
- AppCoordinator — главный координатор, управляющий дочерними.
- ChildCoordinator — отвечает за конкретный поток (например, авторизация или главный экран).
Пример реализации для UIKit:
import UIKit
// MARK: - Coordinator Protocol
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
}
// MARK: - Main Coordinator
class MainCoordinator: Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let vc = ViewController.instantiate()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
}
func showDetail(with id: Int) {
let detailCoordinator = DetailCoordinator(navigationController: navigationController, itemID: id)
childCoordinators.append(detailCoordinator)
detailCoordinator.start()
}
}
// MARK: - Detail Coordinator
class DetailCoordinator: Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
private let itemID: Int
init(navigationController: UINavigationController, itemID: Int) {
self.navigationController = navigationController
self.itemID = itemID
}
func start() {
let vc = DetailViewController.instantiate()
vc.coordinator = self
vc.itemID = itemID
navigationController.pushViewController(vc, animated: true)
}
func goBack() {
navigationController.popViewController(animated: true)
// Удаляем себя из childCoordinators
if let parent = navigationController.viewControllers.first?.coordinator as? MainCoordinator {
parent.childCoordinators.removeAll { $0 is DetailCoordinator }
}
}
}
Когда использовать Coordinator:
- При сложной навигации с несколькими сценариями.
- Для глубоких ссылок (deep linking).
- Для тестирования навигации изолированно.
3. Паттерн Builder (Строитель) для сложных объектов
Builder позволяет создавать сложные объекты пошагово, отделяя процесс конструирования от представления. Особенно полезен для настройки UI-компонентов или конфигурации сетевых запросов.
Пример: построитель кастомного алерта
import UIKit
// MARK: - Product
struct AlertConfig {
let title: String?
let message: String?
let actions: [UIAlertAction]
let preferredStyle: UIAlertController.Style
let tintColor: UIColor?
}
// MARK: - Builder
final class AlertBuilder {
private var title: String?
private var message: String?
private var actions: [UIAlertAction] = []
private var preferredStyle: UIAlertController.Style = .alert
private var tintColor: UIColor?
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