swift

🧩 Протоколы в Swift

Протоколы в Swift — это один из самых мощных инструментов языка, позволяющий описывать контракты поведения между типами.

Они лежат в основе таких концепций, как абстракция, полиморфизм и интерфейсная архитектура. (Кавооо блять?????)

Swift делает работу с протоколами невероятно гибкой — поэтому их активно используют в реальных проектах

🎯 Что такое протокол

Протокол — это набор требований к свойствам, методам и инициализаторам.

Любой тип (структура, класс или перечисление) может подписаться на протокол и реализовать эти требования.

protocol ClearingProtocol {
    var name: String { get }
    
    func clear()
}

Протокол не содержит реализации — только описание.

Это как договор: если тип подписался на него, он обязан выполнять условия.

🚗 Реализация протокола

Чтобы тип соответствовал протоколу, нужно реализовать все его требования.

struct Worker: ClearingProtocol {
    var name: String 
    
    func clear() {
        print("Уборщик \(name) убрал уборную")
    }
}

let workerPetya = Worker(name: "Джанитор")
workerPetya.clear() // "Уборщик Джанитор убрал уборную"

➡️ Теперь Worker официально соответствует ClearingProtocol и способен осуществить уброку помещения

🧱 Свойства в протоколах

Протоколы могут объявлять только интерфейс свойств, но не определяют, где и как они хранятся.

Уже известный нам протокол уборщика гарантирует, что у того, кто возьмёт на себя обязанности убирать – будет имя с типом данных String и функция Убирать

protocol ClearingProtocol {
    var name: String { get set }

	func clear()
}

struct Worker: ClearingProtocol {
    var name: String
    
    func clear() {
        print("Уборщик \(name) убрал уборную")
    }
}

🔧 Расширения протоколов

В Swift можно создавать реализацию по умолчанию для методов протокола через extension.

Это мощный приём, который делает код компактным и позволяет типам внедрять функциональность без дублирования.

protocol Greetable {
    var name: String { get }
    
    func greet()
}

/// Мы расшири протокол и теперь, если вдруг кто-то решит не добавлть функцию greet()
/// она будет взята от сюда
/// Это очень похоже на то как ведут себя в функции в базовом и дочернем классе
extension Greetable {
    func greet() {
        print("👋 Привет, \(name)!")
    }
}

struct User: Greetable {
    var name: String
}

let user = User(name: "Сергей")
user.greet()

Обратите внимание – User не реализует greet() сам, а получает реализацию “по умолчанию” через расширение протокола.

Это может быть удобно, если мы решим, что в протоколе есть функции, которые достаточно очевидны и мы можем задать им реализацию по умолчанию.

🧩 Протоколы как типы

Протоколы можно использовать вместо конкретных типов.

Это позволяет работать с объектами по их поведению, а не по конкретному классу или структуре.

let vehicles: [Drivable] = [
    Car(maxSpeed: 180),
    Bus(maxSpeed: 120, capacity: 40)
]

for vehicle in vehicles {
    vehicle.drive()
}

Такой код идеально иллюстрирует принцип «Программируйте, ориентируясь на интерфейс, а не на реализацию»

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

struct Worker: ICleaning {
    
    let name: String
    
    func clear() {
        print("Занимаюсь уборкой")
    }
 }

А в нашем салоне красоты, мы указали, что у нас есть бригада уборщиков.

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

class BeautySalon {
    
    /// Массив с конкретными Рабочими
    let workers: [Worker]
    
    /// Функция очистки салона
    func clearing() {
        for worker in workers {
            worker.clear()
        }
    }
}

Если мы добавим протокол, это позволит нам возложить обязанности убирать на кого угодно.

Мы можем сказать, что и администратор теперь умеет убирать, достаточно лишь класс Administrator подписать под протокол ICleaning

protocol ICleaning {
    var name: String { get }
    
    func clear()
}

В итоге, когда возникнет ситуация назначить уборщика – мы уже не ограничины лишь только классом Worker – нас устроит любой, кто может убирать.

В итоге наш Салон Красоты будет выглядеть слудующим образом:

class BeautySalon {
    
    /// Массив с кем угодно, главное, что бы была функция "Убирать"
    let workers: [ICleaning]
    
    /// Функция очистки салона
    func clearing() {
        for worker in workers {
            worker.clear()
        }
    }
}

Подобный подход добавляет динамичности и независимости.

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

Это позволит нам не обращать внимания и не переписывать код, когда потребуется изменить реализацию. Если бы мы завязывались на конкретные классы – мы бы всецело от них и зависели, когда мы завязываемся лишь на протокол – у нас есть пространство для манёвра.

В этом деле нужен опыт, пытаться понять это лишь в теореи – лишнее. Практикуйтесь

🧠 Варианты наименования протоколов

Вы верно заметили, что где-то я использую наименование вида CleaningProtocol а где-то ICleaning

I в начале означает, что это “Интерфейс” – это синонимично русскому понятию “Протокол”

Принято использовать именно такое именование – I + Название протокола

🧠 Протоколы класса (class-only)

Иногда полезно ограничить протокол так, чтобы его могли реализовывать только классы.
Для этого используется AnyObject.

protocol Observable: AnyObject {
    func addObserver(_ observer: AnyObject)
}

class Subject: Observable {
    func addObserver(_ observer: AnyObject) {
        print("👀 Добавлен наблюдатель: \(observer)")
    }
}

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

Когда речь зайдёт о сравнении структур и классов – вы вспомните, что есть такая возможность ограничить работу только классам.


⚖️ Протоколы vs Наследование классов

💥 Особенность 🧩 Протокол 🏛 Класс
Множественная реализация ✅ Да ❌ Нет
Требует реализации методов ✅ Да ❌ Нет
Можно использовать со структурами / enum ✅ Да ❌ Нет
Общее состояние / хранение данных ❌ Нет ✅ Да
Расширения и дефолтное поведение ✅ Да ✅ Да

📘 Вывод: протоколы — это гибкая альтернатива наследованию, идеально подходящая для композиции.

🧱 Архитектура на протоколах (пример)

Представим простую структуру приложения с зависимостями через протоколы.

Такой подход делает проект лёгким для тестирования и замены компонентов.

protocol NetworkService {
    func fetchData(from url: String) -> String
}

class APIClient: NetworkService {
    func fetchData(from url: String) -> String {
        print("🌐 Запрос данных с \(url)")
        return "JSON Data"
    }
}

protocol UserRepository {
    func getUser() -> String
}

class DefaultUserRepository: UserRepository {
    private let network: NetworkService

    init(network: NetworkService) {
        self.network = network
    }

    func getUser() -> String {
        return network.fetchData(from: "https://example.com/user")
    }
}

// Используем зависимости через протоколы
let client = APIClient()
let repo = DefaultUserRepository(network: client)
print(repo.getUser())

💡 Такое проектирование упрощает подстановку тестового клиента (MockAPIClient) при юнит-тестах.

🧾 Заключение

Протоколы в Swift — это основа гибкой, чистой и расширяемой архитектуры.

✨ Они позволяют: