Паттерны проектирования в Swift: продвинутый уровень для iOS/macOS

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

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

Начать курс

Паттерны проектирования в 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                

Блоги

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