ГлавнаяБлогПротоколы в Swift: что это и как использовать
Алгоритмы

Протоколы в Swift: что это и как использовать

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

Al
Редакция Algolitalgolit.ru
8 мин чтения5 августа 2026 г.

Протоколы в Swift: что это и зачем они нужны

Протоколы позволяют определить, что умеет тип, не вдаваясь в его конкретную реализацию. Как только вы это осознаете, огромная часть Swift и SwiftUI станет понятной. Представьте, что вы создаете симулятор аниме-битв, и вам нужна функция, которая рассчитывает время, за которое боец доберется до поля боя. Легко — вы пишете её для Ninja:

func sendToMission(distance: Int, using fighter: Ninja) {
    // код здесь
}

Но затем кто-то спрашивает: «А как насчет самурая?» Хорошо:

func sendToMission(distance: Int, using fighter: Samurai) {
    // код здесь
}

Потом: «А пираты? Волшебники? Гигантские мехи?» Внезапно у вас шесть версий одной и той же функции, которые делают одно и то же, отличаясь только типом принимаемого параметра. И каждый раз, когда кто-то придумывает новый тип бойца, вам приходится писать еще одну версию. Есть способ лучше. 🍥

Что такое протокол в Swift?

Протокол — это обещание. Он говорит: «Любой тип, который соответствует мне, должен иметь эти конкретные свойства и методы». Он ничего не реализует. Он не содержит кода за этими обещаниями. Это чисто контракт — список требований, которые типы могут согласиться выполнить.

Вот как выглядит протокол:

protocol Fighter {
    var name: String { get }
    var speed: Int { get set }
    func estimateTravelTime(for distance: Int) -> Int
    func travel(distance: Int)
}

Разберем по частям:

  • protocol Fighter — объявление нового протокола с именем Fighter. Как и все типы в Swift, имя начинается с заглавной буквы.
  • var name: String { get } — каждый Fighter должен иметь свойство name, которое можно читать. Это может быть константа или вычисляемое свойство, главное — доступное для чтения.
  • var speed: Int { get set } — каждый Fighter должен иметь свойство speed, которое можно читать и записывать. Это исключает константы.
  • func estimateTravelTime(for:) и func travel(distance:) — каждый Fighter должен иметь эти два метода. Обратите внимание: без тел функций. Только сигнатуры.

Вот и все. Протоколу не важно, как это реализовано. Он просто говорит: если хочешь называться Fighter, ты должен это иметь.

Соответствие протоколу: делаем типы бойцами

Теперь создадим несколько реальных бойцов. Синтаксис соответствия протоколу выглядит так же, как наследование от родительского класса — двоеточие после имени типа:

struct Ninja: Fighter {
    let name = "Naruto"
    var speed = 85

    func estimateTravelTime(for distance: Int) -> Int {
        distance / speed
    }

    func travel(distance: Int) {
        print("\(name) использует Технику Уклонения, чтобы преодолеть \(distance) км!")
    }

    func shadowClone() {
        print("Теневое клонирование!")
    }
}

struct Samurai: Fighter {
    let name = "Rurouni"
    var speed = 60

    func estimateTravelTime(for distance: Int) -> Int {
        distance / speed
    }

    func travel(distance: Int) {
        print("\(name) быстро скачет на лошади \(distance) км.")
    }

    func drawSword() {
        print("Баттодзюцу!")
    }
}

Обратите внимание на несколько моментов:

  • Обе структуры соответствуют Fighter, реализуя все четыре обязательных члена. Если бы у одной из них не хватало хотя бы одного, Swift отказался бы компилировать код.
  • У Ninja есть дополнительный метод (shadowClone()), а у Samurai — (drawSword()). Это нормально — протокол определяет минимум, а не максимум.
  • name — константа let в обоих случаях, что удовлетворяет { get }, так как константы доступны для чтения.
  • speed — переменная var в обоих случаях, что удовлетворяет { get set }, так как переменные можно и читать, и записывать.

Использование протокола как типа: главная выгода

Теперь все сходится. Поскольку Swift знает, что любой Fighter имеет estimateTravelTime() и travel(), мы можем написать функцию, принимающую Fighter вместо конкретного типа:

func sendToMission(distance: Int, using fighter: Fighter) {
    if fighter.estimateTravelTime(for: distance) > 10 {
        print("Это долгий путь. \(fighter.name) лучше подготовиться.")
    } else {
        fighter.travel(distance: distance)
    }
}

Теперь эта единственная функция работает с любым типом, соответствующим Fighter:

let naruto = Ninja()
let rurouni = Samurai()

sendToMission(distance: 500, using: naruto)
sendToMission(distance: 500, using: rurouni)

Swift знает, что estimateTravelTime() и travel() существуют у обоих типов, потому что это гарантирует протокол. И внутри функции, когда travel() вызывается на Ninja, выводится версия ниндзя. Когда вызывается на Samurai — версия самурая. Один и тот же вызов функции, разное поведение, автоматически.

Массивы протоколов: гетерогенные коллекции

Становится еще мощнее. Вы можете поместить разные типы в один массив, если все они соответствуют одному протоколу:

func calculateAllTravelTimes(fighters: [Fighter], distance: Int) {
    for fighter in fighters {
        let time = fighter.estimateTravelTime(for: distance)
        print("\(fighter.name) потребуется \(time) часов, чтобы преодолеть \(distance) км")
    }
}

calculateAllTravelTimes(fighters: [naruto, rurouni], distance: 300)

Массив [Fighter] может содержать Ninja, Samurai или любой другой тип, соответствующий Fighter. Swift не заботится о конкретном типе в массиве — ему важно только, что все элементы выполнили обещания протокола.

Почему не просто наследование?

Вы можете спросить: нельзя ли сделать то же самое с родительским классом? Иметь класс Fighter, от которого наследуются Ninja и Samurai? Можно, но у протоколов есть два больших преимущества:

  1. Структуры и перечисления тоже могут соответствовать протоколам. В Swift наследоваться от классов могут только классы. Но протоколы работают со структурами, классами и перечислениями одинаково. Поскольку большинство типов в Swift — структуры, это очень важно.
  2. Можно соответствовать нескольким протоколам одновременно. Тип может иметь только один родительский класс, но может соответствовать скольким угодно протоколам, просто перечислив их через запятую:
struct Ninja: Fighter, Codable, CustomStringConvertible {
    // должен удовлетворять всем трем протоколам
}

Вот почему разработчики Swift любят протоколы — они гибки так, как наследование не может.

Важное замечание: { get } и { get set }

Эти два аннотации легко упустить из виду, но их важно правильно понимать:

  • { get } — свойство должно быть доступно для чтения. Можно использовать константу let, переменную var или вычисляемое свойство с геттером.
  • { get set } — свойство должно быть доступно для чтения и записи. Можно использовать только переменную var или вычисляемое свойство с геттером и сеттером. Константа let не подойдет.

Если попытаться удовлетворить требование { get set } с помощью let, Swift сообщит об ошибке соответствия.

Главное, что нужно запомнить

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

А в SwiftUI протоколы буквально повсюду. View — сам по себе протокол. Identifiable — протокол. Codable — протокол. Вы использовали их с первой статьи о SwiftUI — теперь вы точно знаете, что это такое и как они работают. 🌸

Практический вывод: начните применять протоколы сейчас

Прямо сейчас откройте свой проект и найдите функцию, которая принимает конкретный тип, но могла бы принимать протокол. Определите протокол с нужными свойствами и методами, заставьте тип соответствовать ему и замените параметр функции на этот протокол. Вы сразу почувствуете гибкость. А если вы еще не используете протоколы в SwiftUI — обратите внимание, что View — это протокол, и создайте свой первый кастомный вью-модификатор, используя протокол ViewModifier. Так вы закрепите знания на практике.

#протоколы#Swift#SwiftUI#абстракция
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →