Protocol composition — это механизм в Swift, позволяющий создавать тип, который должен соответствовать нескольким протоколам одновременно. Это альтернативный способ заменить множественное наследование, которого в Swift для классов нет.
История вопроса
Objective-C поддерживал множественное наследование только для протоколов, но не для классов. Swift развивает эту традицию, делая упор на протоколы и их комбинацию для построения новых абстракций.
Проблема
Перед программистом часто стоит задача создать тип, поведение которого определяется несколькими абстракциями. Множественное наследование неизбежно приводит к конфликтам иерархий, в Swift это решается безопасно за счет протоколов и protocol composition.
Решение
В Swift можно комбинировать протоколы с помощью оператора '&'. Это позволяет создавать переменные или параметры функций, которые должны соответствовать сразу нескольким протоколам.
Пример кода:
protocol Drivable { func drive() } protocol Flyable { func fly() } struct FlyingCar: Drivable, Flyable { func drive() { print("Driving") } func fly() { print("Flying") } } func testVehicle(_ vehicle: Drivable & Flyable) { vehicle.drive() vehicle.fly() } testVehicle(FlyingCar())
Ключевые особенности:
Можно ли передать объект, реализующий только один из протоколов, в параметр с protocol composition?
Нет, объект должен реализовывать все протоколы, участвующие в composition, иначе компилятор выдаст ошибку.
Пример кода:
// struct Car: Drivable {} — нельзя передать в testVehicle, ведь fly() не реализован
Работает ли protocol composition с типами, а не только со значениями?
Protocol composition применяется к значениям (переменным, параметрам функций), но не используется при определении типа объекта (например, нельзя объявить новый тип как какую-то «композицию» протоколов — только переменная).
Пример кода:
var obj: SomeProtocol & AnotherProtocol // допустимо // typealias MyType = SomeClass & AnotherProtocol // ошибка
Можно ли комбинировать классы и протоколы через &?
Да, но с одним ограничением: только один классный тип (класс или его наследник) может быть слева, остальные — только протоколы, иначе компилятор выдаст ошибку.
Пример кода:
class A {} protocol B {} // func f(obj: A & B) {} // допустимо // func f(obj: A & AnotherClass & B) {} // ошибка! Только один классный тип разрешён
В проекте для передачи данных между слоями используются объекты с protocol composition, хотя достаточно было одного протокола:
func present(item: Displayable & Serializable) { ... }
Плюсы:
Использование protocol composition только в явных случаях — например, обработка объектов, одновременно поддерживающих Codable и Identifiable для обобщенной сериализации:
func save<T: Codable & Identifiable>(_ item: T) { ... }
Плюсы: