Протоколы в 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 + Название протокола
Иногда полезно ограничить протокол так, чтобы его могли реализовывать только классы.
Для этого используется AnyObject.
protocol Observable: AnyObject {
func addObserver(_ observer: AnyObject)
}
class Subject: Observable {
func addObserver(_ observer: AnyObject) {
print("👀 Добавлен наблюдатель: \(observer)")
}
}
Протокол AnyObject говорит о том, что тот кто реализует этот протокол – обязательно должен быть классом, а не структурой.
Когда речь зайдёт о сравнении структур и классов – вы вспомните, что есть такая возможность ограничить работу только классам.
| 💥 Особенность | 🧩 Протокол | 🏛 Класс |
|---|---|---|
| Множественная реализация | ✅ Да | ❌ Нет |
| Требует реализации методов | ✅ Да | ❌ Нет |
| Можно использовать со структурами / 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 — это основа гибкой, чистой и расширяемой архитектуры.
✨ Они позволяют:
Interface Segregation и Dependency Inversion)