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

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

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

Начать курс

Паттерны проектирования в Swift: когда и зачем

Вы когда-нибудь переписывали один и тот же код по десять раз? Или, открыв свой старый проект через месяц, с ужасом понимали, что ничего не можете в нём понять? Добро пожаловать в клуб. Обычно это происходит, когда архитектура приложения строится «на коленке», без какой-либо системы. И тут на помощь приходят паттерны проектирования в Swift — проверенные временем шаблоны, которые превращают хаос в стройную, понятную структуру.

В этой статье мы не будем сухо перечислять определения. Мы разберёмся, когда реально стоит применять каждый паттерн, а когда он лишь усложнит жизнь. Готовы навести порядок в своём коде? Поехали.

Что такое паттерны проектирования и почему они важны для Swift?

Паттерн проектирования — это не готовый кусок кода, а проверенная схема решения типовой задачи. Представьте, что вы строите дом. Вместо того чтобы каждый раз изобретать, как соединить балки, вы используете стандартные узлы — болты, гайки, уголки. Паттерны — те же «строительные узлы» для программиста.

В Swift, с его мощной системой протоколов и value types, многие паттерны приобретают новое звучание. Например, делегат здесь не просто интерфейс, а изящное решение на протоколах. А синглтон в Swift может быть реализован на let-константе без лишнего кода.

Если вы пишете на Swift и не используете паттерны, рано или поздно вы столкнётесь с проблемами:

  • Жёсткая связанность классов — изменение в одном месте ломает всё.
  • Дублирование кода — одни и те же алгоритмы размазаны по всему проекту.
  • Трудность тестирования — логика не изолирована.

Хватит страдать. Давайте разберём пять самых полезных паттернов для iOS-разработчика.

1. Singleton (Одиночка) — когда объект должен быть в единственном экземпляре

Самый популярный и, пожалуй, самый опасный паттерн. Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему.

Когда использовать:

  • Управление настройками приложения (UserDefaults).
  • Работа с сетью (URLSession.shared).
  • Логирование.
  • Менеджер Core Data (persistent container).

Когда не стоит: Если вы делаете синглтон для каждого второго класса — это антипаттерн. Он усложняет тестирование и создаёт скрытые зависимости.

Пример на Swift:

final class NetworkManager {
    static let shared = NetworkManager()
    
    private init() {} // Приватный инициализатор — ключевой момент
    
    func fetchData(from url: URL) async throws -> Data {
        // Реальная логика запроса
        return Data()
    }
}

// Использование
let data = try await NetworkManager.shared.fetchData(from: url)

Обратите внимание на private init(). Без него любой сможет создать второй экземпляр, и паттерн потеряет смысл.

2. Factory Method (Фабричный метод) — когда создание объекта — это целая наука

Иногда логика создания объекта слишком сложна, чтобы помещать её в конструктор. Или же тип объекта должен определяться на лету. Factory Method выносит создание в отдельный метод, который может быть переопределён в подклассах.

Когда использовать:

  • Создание разных типов view controller'ов в зависимости от состояния.
  • Генерация моделей из JSON с разными полями.
  • Создание UI-элементов с разными стилями.

Пример на Swift:

protocol Button {
    var title: String { get }
    func render()
}

struct PrimaryButton: Button {
    var title: String
    func render() { print("Синяя кнопка: \(title)") }
}

struct SecondaryButton: Button {
    var title: String
    func render() { print("Серая кнопка: \(title)") }
}

enum ButtonType {
    case primary, secondary
}

// Фабричный метод
func createButton(type: ButtonType, title: String) -> Button {
    switch type {
    case .primary:
        return PrimaryButton(title: title)
    case .secondary:
        return SecondaryButton(title: title)
    }
}

// Использование
let button = createButton(type: .primary, title: "Отправить")
button.render() // "Синяя кнопка: Отправить"

Теперь, если вы захотите добавить новый тип кнопки, вам не придётся переписывать всю логику — просто добавляете новый case в фабрику.

3. Delegate (Делегат) — когда нужно, чтобы один объект работал за другой

Один из самых распространённых паттернов в Cocoa Touch. Делегат позволяет одному объекту делегировать (поручать) выполнение задач другому объекту. Это классическая реализация "один-к-одному".

Когда использовать:

  • Обработка событий UI (например, нажатие на ячейку таблицы).
  • Передача данных между контроллерами без создания жёсткой связи.
  • Реализация кастомного поведения для стандартных компонентов.

Пример на Swift:

protocol ImageDownloadDelegate: AnyObject {
    func downloadDidStart()
    func downloadDidFinish(with image: UIImage?)
    func downloadDidFail(error: Error)
}

class ImageDownloader {
    weak var delegate: ImageDownloadDelegate?
    
    func download(from url: URL) {
        delegate?.downloadDidStart()
        // Симуляция загрузки
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
            let image = UIImage() // Представим, что это загруженное изображение
            self.delegate?.downloadDidFinish(with: image)
        }
    }
}

class ViewController: UIViewController, ImageDownloadDelegate {
    let downloader = ImageDownloader()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        downloader.delegate = self
        downloader.download(from: URL(string: "https://example.com/image.jpg")!)
    }
    
    func downloadDidStart() {
        print("Начинаем загрузку...")
    }
    
    func downloadDidFinish(with image: UIImage?) {
        print("Загрузка завершена! Изображение получено.")
    }
    
    func downloadDidFail(error: Error) {
        print("Ошибка загрузки: \(error.localizedDescription)")
    }
}

Обратите внимание на weak var delegate. Это обязательное условие, чтобы избежать retain cycle (утечки памяти).

4. Observer (Наблюдатель) — когда о событии должны узнать все

В отличие от делегата, который работает один-к-одному, Observer позволяет одному объекту (субъекту) уведомлять множество подписчиков о своём состоянии. В Swift для этого часто используют Combine или NotificationCenter.

Когда использовать:

  • Уведомления о смене темы или языка в приложении.
  • Обновление UI при изменении данных в модели (MVVM).
  • События, которые могут быть интересны многим модулям (например, авторизация пользователя).

Пример на Swift с NotificationCenter:

extension Notification.Name {                

Блоги

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