Основные паттерны проектирования Swift

Основные паттерны проектирования Swift

Swift — это мощный язык программирования, который появился в 2014 году и скоро стал одним из самых популярных языков для разработки мобильных приложений для iOS. Он был создан компанией Apple для разработки приложений для iOS, macOS, watchOS и tvOS. Одним из ключевых элементов при разработке на Swift являются паттерны проектирования, которые помогают упростить и оптимизировать код. В этой статье мы рассмотрим некоторые основные паттерны проектирования, которые можно использовать при разработке приложений на языке Swift.

1. MVC (Model-View-Controller)

MVC — это один из самых распространенных паттернов проектирования при разработке на Swift. Он заключается в разделении приложения на три основных компонента: модель (Model), представление (View) и контроллер (Controller).

Модель отвечает за хранение данных и бизнес-логику приложения. Представление отвечает за отображение данных пользователю, а контроллер управляет взаимодействием между моделью и представлением.

Пример кода:


class Model {
    var data: String = ""
    
    func fetchData() {
        // запрос данных с сервера
        data = "Данные получены"
    }
}

class View {
    var label: UILabel
    
    init(label: UILabel) {
        self.label = label
    }
    
    func showData(data: String) {
        label.text = data
    }
}

class Controller {
    var model: Model
    var view: View

    init(model: Model, view: View) {
        self.model = model
        self.view = view
    }
    
    func viewDidLoad() {
        model.fetchData()
        view.showData(data: model.data)
    }
}

let model = Model()
let view = View(label: UILabel())
let controller = Controller(model: model, view: view)

controller.viewDidLoad()

2. MVP (Model-View-Presenter)

MVP — это вариация паттерна MVC, который уделяет больше внимания разделению логики приложения на модель и представление.

Пример кода:


protocol ModelProtocol {
    var data: String { get set }
    func fetchData(completion: @escaping () -> Void)
}

class Model: ModelProtocol {
    var data: String = ""
    
    func fetchData(completion: @escaping () -> Void) {
        // запрос данных с сервера
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.data = "Данные получены"
            completion()
        }
    }
}

protocol ViewProtocol: AnyObject {
    func showData(data: String)
}

class View: UIViewController, ViewProtocol {
    var label: UILabel
    
    init(label: UILabel) {
        self.label = label
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = "Загрузка данных..."
    }
    
    func showData(data: String) {
        label.text = data
    }
}

class Presenter {
    var model: ModelProtocol
    weak var view: ViewProtocol?
    
    init(model: ModelProtocol, view: ViewProtocol) {
        self.model = model
        self.view = view
    }
    
    func viewDidLoad() {
        model.fetchData { [weak self] in
            self?.view?.showData(data: self?.model.data ?? "")
        }
    }
}

let model = Model()
let view = View(label: UILabel())
let presenter = Presenter(model: model, view: view)

view.presenter = presenter
presenter.viewDidLoad()

3. MVVM

Model View ViewModel

Model View ViewModel (MVVM) является популярным паттерном проектирования, который разделяет приложение на три основных компонента: модель, представление и модель представления. Этот паттерн помогает создать легко-поддерживаемый, легко-расширяемый и модульный код. Вот пример реализации этого паттерна:


    class Model {
    var data: String = ""
    
    func fetchData(completion: @escaping () -> Void) {
        // запрос данных с сервера
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.data = "Данные получены"
            completion()
        }
    }
}

struct ViewModel {
    var model: Model
    
    var data: String {
        return model.data
    }
    
    func fetchData(completion: @escaping () -> Void) {
        model.fetchData {
            completion()
        }
    }
}

class View: UIViewController {
    var label: UILabel
    var viewModel: ViewModel
    
    init(label: UILabel, viewModel: ViewModel) {
        self.label = label
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = "Загрузка данных..."
        viewModel.fetchData { [weak self] in
            self?.label.text = self?.viewModel.data
        }
    }
}

let model = Model()
let viewModel = ViewModel(model: model)
let view = View(label: UILabel(), viewModel: viewModel)


  

4. Delegation

Delegate pattern in Swift

Delegation — это паттерн, который позволяет объектам передавать свои обязанности и ответственности другим объектам. Использование делегата позволяет разделить более крупные классы на более мелкие, более логически связанные классы. Вот пример реализации делегата в Swift:


    protocol AlarmDelegate {
        func alarmDidRing()
    }
    
    class Alarm {
        var delegate: AlarmDelegate?
        
        func ring() {
            delegate?.alarmDidRing()
        }
    }
    
    class ViewController: UIViewController, AlarmDelegate {
        func alarmDidRing() {
            // handle alarm ringing event
        }
        
        let alarm = Alarm()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            alarm.delegate = self
            alarm.ring()
        }
    }
  

5. Singleton

Singleton — это паттерн, который гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру. Этот шаблон проектирования обычно используется для создания глобального объекта, например, объекта приложения или объекта настроек. Во многих случаях наличие нескольких экземпляров какого либо объекта программы усложнит операции обновления или сделает более сложным синхронизацию данных между всеми экземплярами.

Вот пример реализации синглтона в Swift:


    class Settings {
        static let shared = Settings()
        
        var soundEnabled = true
        var notificationsEnabled = true
    }
    
    class ViewController: UIViewController {
        let settings = Settings.shared
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // use settings object
        }
    }
  

6. Facade

Facade

Facade — это паттерн, который скрывает сложность системы, предоставляя упрощенный интерфейс для взаимодействия с ней. Фасад предоставляет более простой интерфейс, который позволяет клиентскому коду взаимодействовать с сложной системой без необходимости знать о ее внутреннем устройстве. Вот пример реализации фасада в Swift:


    class Facade {
        let subsystem1 = Subsystem1()
        let subsystem2 = Subsystem2()
        let subsystem3 = Subsystem3()
        
        func operation() {
            subsystem1.operation1()
            subsystem2.operation2()
            subsystem3.operation3()
        }
    }
    
    class Subsystem1 {
        func operation1() {
            // do something
        }
    }
    
    class Subsystem2 {
        func operation2() {
            // do something
        }
    }
    
    class Subsystem3
{
        func operation3() {
            // do something
        }
    }
    
    class ViewController: UIViewController {
        let facade = Facade()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            facade.operation()
        }
    }
  

7. Builder

Builder — это паттерн, который позволяет создавать сложные объекты с помощью последовательности простых шагов. Он особенно полезен при создании объектов со многими параметрами.

Пример кода:


class Person {
    var name: String
    var age: Int
    var email: String
    
    init(name: String, age: Int, email: String) {
        self.name = name
        self.age = age
        self.email = email
    }
}

class PersonBuilder {
    private var name: String?
    private var age: Int?
    private var email: String?
    
    func setName(name: String) -> PersonBuilder {
        self.name = name
        return self
    }
    
    func setAge(age: Int) -> PersonBuilder {
        self.age = age
        return self
    }
    
    func setEmail(email: String) -> PersonBuilder {
        self.email = email
        return self
    }
    
    func build() -> Person? {
        guard let name = name, let age = age, let email = email else {
            return nil
        }
        
        return Person(name: name, age: age, email: email)
    }
}

let person = PersonBuilder()
                .setName(name: "Иван")
                .setAge(age: 25)
                .setEmail(email: "ivan@example.com")
                .build()

print(person?.name ?? "")

8. Factory Method

Factory Method — это паттерн, который предоставляет интерфейс для создания объектов, но позволяет подклассам выбирать класс для создания. Таким образом, объекты могут создаваться динамически в зависимости от определенных условий.

Пример кода:


protocol Animal {
    var name: String { get }
    func makeSound()
}

class Dog: Animal {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func makeSound() {
        print("Гав-гав")
    }
}

class Cat: Animal {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func makeSound() {
        print("Мяу")
    }
}

enum AnimalType {
    case dog(name: String)
    case cat(name: String)
}

class AnimalFactory {
    static func createAnimal(type: AnimalType) -> Animal? {
        switch type {
        case .dog(let name):
            return Dog(name: name)
        case .cat(let name):
            return Cat(name: name)
        }
    }
}

let dog = AnimalFactory.createAnimal(type: .dog(name: "Бобик"))
let cat = AnimalFactory.createAnimal(type: .cat(name: "Мурка"))

dog?.makeSound()
cat?.makeSound()

9. Observer

Observer — это паттерн, который позволяет уведомлять другие объекты об изменениях состояния наблюдаемого объекта. Он основан на принципе издатель-подписчик.

Пример кода:


protocol Observer: AnyObject {
    func update(count: Int)
}

protocol Subject {
    var observers: [Observer] { get set }
    
    func addObserver(observer: Observer)
    func removeObserver(observer: Observer)
    func notify()
}

class Counter: Subject {
    var count: Int = 0
    var observers: [Observer] = []
    
    func addObserver(observer: Observer) {
        observers.append(observer)
    }
    
    func removeObserver(observer: Observer) {
        observers.removeAll(where: { $0 === observer })
    }
    
    func notify() {
        observers.forEach { $0.update(count: count) }
    }
    
    func increment() {
        count += 1
        notify()
    }
}

class View: Observer {
    func update(count: Int) {
        print("Счетчик: \(count)")
    }
}

let counter = Counter()
let view = View()

counter.addObserver(observer: view)
counter.increment()

10. Decorator

Decorator — это паттерн, который позволяет динамически добавлять новое поведение или функциональность объекту без изменения его исходного кода.

Пример кода:


protocol Beverage {
    var description: String { get }
    var cost: Double { get }
}

class Espresso: Beverage {
    var description: String {
        return "Эспрессо"
    }
    
    var cost: Double {
        return 1.5
    }
}

class Milk: Beverage {
    private let beverage: Beverage
    
    init(beverage: Beverage) {
        self.beverage = beverage
    }
    
    var description: String {
        return beverage.description + ", молоко"
    }
    
    var cost: Double {
        return beverage.cost + 0.5
    }
}

class Sugar: Beverage {
    private let beverage: Beverage
    
    init(beverage: Beverage) {
        self.beverage = beverage
    }
    
    var description: String {
        return beverage.description + ", сахар"
    }
    
    var cost: Double {
        return beverage.cost + 0.3
    }
}

var beverage: Beverage = Espresso()
print(beverage.description)
print(beverage.cost)

beverage = Milk(beverage: beverage)
print(beverage.description)
print(beverage.cost)

beverage = Sugar(beverage: beverage)
print(beverage.description)
print(beverage.cost)

Swift предоставляет множество возможностей для создания простых и эффективных приложений. Разные паттерны проектирования могут помочь улучшить качество кода, упростить сложные системы и повысить их масштабируемость. Эта статья представляет только небольшую часть доступных паттернов для Swift, но использование этих базовых шаблонов может помочь в разработке высококачественного кода.