Предисловие
Материал является примером, который демонстрирует возможности языка и не претендует на звание «хороший код».
Материал предназначен начинающим разработчикам для общего ознакомления.
Весь свой информационный мусор, я коллекционирую на своей стене в ВК, так что добро пожаловать.
Немного от себя
Вот уж не знаю почему, но многих протоколы пугают, их боятся использовать, понимать и в целом избегают как огня, что является в корне неверным выбором. Иными словами, если Вам не нравятся протоколы, Вы просто не умеете их готовить.
Если разобраться поглубже, язык Swift, является крайне продуманным и собрал в себе все лучшее из многообразия языков. Он быстрый, продуманный и очень оптимизированный. Каждый его инструмент, всегда преследует определенную цель и не всегда понятно с первого раза, какую же такую цель преследует изучаемый инструмент, но поверьте, цель есть всегда. Так и у протоколов есть масса возможностей и перекочевали они с Objective-C не просто так. Начнем мы с простого и плавно перейдем к более сложному (хотя протоколы весьма просты в усвоении и очень даже функциональны).
Вопреки критике, я буду все так же, подробно, разбирать каждый момент т.к. напоминаю, что материал предназначен именно для начинающих разработчиков. Ну что же, давайте начинать.
Что такое протокол
Давайте начнем с постановки простой, фейковой задачи. Нам необходимо разработать функцию, которая получает некий класс/структуру, берет свойство count и возвращает нам квадрат этого значения, пускай в Int. Эту функцию мы будем передавать другим разработчикам, чтобы они могли её использовать.
Казалось бы, в чем проблема? Но давайте подумаем... Какой тип данных указать во входных параметрах функции? Any? Не очень удачное решение т.к. никто не знает какой тип данных будет использован в ней и приведение типов выполнить не получится.
Можно конечно решить ситуацию посредством дженериков, но это тема для другой публикации.
Подумайте немного, над этой задачей. Она не так проста как кажется на первый взгляд и все было бы печально, если бы не протоколы.
Посмотрите на код ниже, и начинайте постигать убийственную мощь протоколов.
protocol MyFirstProtocol {
var count: Int { get }
}
func multipliesNumberBySelf(_ type: MyFirstProtocol) -> Int {
type.count * type.count
}
Внезапно и удивительно, но оказывается, что протокол является типом данных. Мы можем его создать, описать и использовать. В момент создания протокола, Вы просто описываете содержимое потенциального типа, который будет его использовать (будет следовать протоколу, будет подписан на протокол и т.д.), а потом в момент создания типа, указываете протокол, чтобы сказать компилятору «привет, я класс и я следую протоколу», если тип не соответствует протоколу, Xcode выдаст подсказку и в большинстве случаев, даже предложит исправить несоответствие. В коде выше, как Вы уже вероятно догадались, мы создали простой протокол, который декларирует простое свойство count и его тип данных, не более (конструкция { get }, говорит о том, что свойство должно быть доступно для чтения) А потом, с его помощью(протокола), создали функцию из первоначальной задачи. Давайте теперь создадим класс, который соответствует протоколу.
class MegaClass: MyFirstProtocol {
let count: Int = 5
}
синтаксис такой же как при наследовании классов
Так как у нас были указания исключительно о необходимости наличия свойства count для чтения, нам никто не запрещает использовать константу и класс выше соответствует декларации. Теперь создадим структуру под наш протокол.
struct MegaStruct : MyFirstProtocol {
var count: Int = 8
var name: String = "Dart Vader"
}
У нее есть, еще свойство, помимо count, но это не является нарушением протокола т.к. протокол говорить только о том, что должно быть, а не то, чего быть не должно. Давайте попробуем это с нашей функцией.
Все работает. Давайте добавим классу MegaClass, свойство property для наглядности. Теперь обратите внимание на важный момент. Если мы будем обращаться к типу, который соответствует протоколу внутри функции, даже при наличии иных свойств/методов, нам будет доступно только описанное в протоколе. Грубо говоря, протокол определяет, что конкретно есть у типа и дает возможность с этим взаимодействовать.
Точно таким же образом, мы можем поместить разные типы в массив если они соответствуют определенному протоколу.
В общем протокол простыми словами это - тип данных, декларация описывающая тип и средство обобщения различных типов т.к. его можно использовать с class, struct, enum. Теперь я думаю понятно, когда вы будете передавать свой код другому разработчику, он всегда сможет привести свои типы к соответствию с необходимым протоколом без каких-либо сложностей.
Наследование протоколов
Конечно же протоколы умеют в наследование, причем множественное и типы данных могут соответствовать нескольким протоколам
//Второй протокол
protocol MySecondProtocol {
var number: Int { get }
}
//Протокол, который наследует другие протоколы
protocol ChildrenProtocol: MySecondProtocol, MyFirstProtocol {}
//Подписываемся на соблюдение нескольких протоколов
class classForMultipleInheritance: MySecondProtocol, MyFirstProtocol {
var number: Int = 2
var count: Int = 2
}
//Подписываемся на протокол, который наследует другие протоколы
class classForInheritingMultipleProtocol : ChildrenProtocol {
var number: Int = 2
var count: Int = 2
}
Если класс имеет суперкласс в виде класса, который соответствует какому-либо протоколу, то естественно он так же будет ему соответствовать.
Опять же ожидаемо и использование подобных типов в работе без каких либо проблем. Давайте для более простого понимая, представим следующее:
протоколы Папа, Мама и общий протокол Ребенок.
Некий класс Класс, который соответствует протоколу Ребенок у которого есть подкласс Подкласс.
Некая функция/массив/что-либо еще, умеющие работать с типами, которые соответствуют протоколу Мама
Мы можем передать в функцию/массив и т.д. которые требуют соответствие протоколу Мама как Класс так и Подкласс т.к. они оба соответствую ему (протоколу Мама) через наследование. Эта логичная концепция конечно бывает сложна для понимания, но на самом деле очень проста. Необходимо просто переварить. А еще лучше просто побаловаться в коде. Ничего не заменит практику.
Если классу необходимо наследовать какой-либо класс и соответствовать протоколу/протоколам, то первым указывается суперкласс, а далее идут протоколы.
class ParentClass {}
class ChildClass: ParentClass, MyFirstProtocol, MySecondProtocol {
var count: Int = 3
var number: Int = 3
}
Протоколы могут декларировать так же методы и инициализаторы, но про инициализаторы в протоколах лучше говорить в отдельной статье т.к. это обширный материал, пока могу сказать, что инициализатор следует помечать как обязательный для потенциальных подклассов, через required
protocol InitProtocol {
var property: String { get }
init(_ property: String)
}
class ExampleClass : InitProtocol {
var property: String
required init(_ property: String) {
self.property = "свойство"
}
}
как видно из листинга выше, нам необязательно использовать аргумент инициализатора в подобной реализации т.к. протокол определяет инициализатор, а не его тело. С методами та же история, почти...
protocol MethodProtocol {
func method(_ value: String) -> Int
}
class ExampleClass1 : MethodProtocol {
func method(_ value: String) -> Int {
5
}
}
class ExampleClass2 : MethodProtocol {
func method(_ value: String) -> Int {
if value.count > 5 {
return 10
} else {
return 5
}
}
}
class ExampleClass3 : MethodProtocol {
func method(_ value: String) -> Int {
self.secondMethod()
}
func secondMethod() -> Int {
10
}
}
обратите внимание - для удобства восприятия, я помечаю типы цифрами, никогда не делайте так в коде
мы определяем тип метода, а что в нем происходит, протокол не интересует
Если метод должен изменять значения, то его следует пометить как mutating, это не обязательное требование, но его игнорирование заблокирует нормальное использование такого протокола со структурами.
protocol MethodProtocol {
mutating func setProperty(_ value: Int)
}
class ExampleClass : MethodProtocol {
var property: Int = 0
func setProperty(_ value: Int){
self.property = value
}
}
struct ExampleStruct : MethodProtocol {
var property: Int = 0
mutating func setProperty(_ value: Int){
self.property = value
}
}
Хотя протоколы можно использовать с различными типами, мы можем ограничить протокол только для использования с классами
protocol MyProtocol: AnyObject { }
ранее использовалось для декларирования слабых ссылок (в Swift 5 вызовет ошибку)
protocol MyProtocol: AnyObject {
weak var property: MyClass? { get set }
}
обратите внимание констукция { get set } говорит о том, что свойство должно уметь менять значение т.е. константой его объявлять нельзя, только переменной. И как обсуждалось ранее, get разрешает использовать константы. Использовать set без get нельзя.
сейчас практическое применение может понадобится, например для использования опциональных методов. Это невозможно в Swift реализации, но было возможно в Objective-c, а так как Swift обратно совместим с ним, мы все же можем использовать эту возможность.
import Foundation
@objc protocol MyProtocol {
@objc optional func method()
}
не забудте импортировать Foundation для доступа к @objc
но в таком случае у нас нет жесткой необходимости использовать AnyObject т.к. подобный протокол и так не будет функционировать со структурами.
Соответственно опциональные методы как следует из названия не являются обязательными и класс без них, вполне себе будет соответствовать протоколу
import Foundation
@objc protocol MyProtocol {
@objc optional func method()
@objc optional func secondMethod()
}
class MyClass: MyProtocol {
func method(){
//
}
}
Расширения протоколов
Ну вот мы и добрались до истинной мощи протоколов. Казалось бы, ну декларация, ну удобная, но мне и так нормально, сами используйте свои протоколы, мне и так хорошо. Работаю я один, свой код знаю, все пучком. Нет! Это ошибочное суждение!Уверен сейчас, ваше мировоззрение изменится раз и навсегда, а первая мысль будет «черт, почему я не использовал это раньше...», начнем.
protocol Example1 {
var property1: Int { get }
}
protocol Example2 {
var property2: Int { get }
}
protocol SuperProtocol: Example1, Example2 {}
struct StructExample: SuperProtocol {
var property1: Int = 5
var property2: Int = 5
}
class ClassExample: SuperProtocol {
var property1: Int = 5
var property2: Int = 5
}
var myClass = ClassExample()
var myStruct = StructExample()
Сейчас вам должно уже быть все понятно. Есть два протокола, который декларируют по свойству. Протокол который наследуется от них и два типа, класс со структурой. А теперь добавим вот такой код
extension Example1 {
func method() {
print("мое свойство property1 - \(self.property1)")
}
}
extension Example2 {
mutating func setProperty(_ value: Int) {
self.property2 = value
}
}
extension SuperProtocol {
var sumProperty: Int { self.property1 + self.property2 }
}
Однако здравствуйте, я думаю вы уже поняли, все эти возможности появляются у наших типов, которые соответствуют протоколу в доступе.
В расширениях протокола можно не только объявлять методы, но и задавать им логику. Дополнять протокол вычисляемыми значениями и даже определять инициализатор, но с нюансами. Давайте изменим наш код
protocol SuperProtocol: Example1, Example2 {
init()
}
добавим инициализатор в протокол, это заставит нас изменить наш класс (структуры не касается т.к. структуры не наследуются)
class ClassExample: SuperProtocol {
var property1: Int
var property2: Int
required init() {
self.property1 = 5
self.property2 = 5
}
}
и расширим наш SuperProtocol дополнительным инициализатором
extension SuperProtocol {
var sumProperty: Int { self.property1 + self.property2 }
init(property2: Int){
self.init() //необходимо
self.property2 = property2
}
}
нам необходимо вызывать существующий инициализатор в расширении, гарантируя таким образом полную инициализацию типа. Но как я уже говорил ранее, инициализаторы в протоколах - отдельная история.
Нюансы и особенности работы с протоколами
Диспетчеризация методов, посмотрите на код
protocol ProtocolExample {}
class ClassExample: ProtocolExample {
func method() {
print("метод класса")
}
}
extension ProtocolExample {
func method() {
print("метод протокола")
}
}
как думаете, какой метод будет вызван?
method dispatch работает таким образом, что отдает приоритет типу, а константа myClass относится к типу ClassExample т.к. неявное присваивание типа, определяет её как класс, а не протокол. Но если мы явно укажем тип ProtocolExample, то будет вызван метод протокола.
let myClass: ProtocolExample = ClassExample()
Соответственно во всех функциях/методах/массивах и т.д., во всем, что работает с типом протокола, приоритет будет за методом протокола.
2. Можно перечислять несколько протоколов для взаимодействия
3. В протоколах нельзя определять доступы (private и т.д.), но можно сделать вот так
За сим, я думаю достаточно. Еще конечно есть сопоставления протоколов, но там уже будет необходимо понимание дженериков, протоколы с использованием дженериков, а дженерики это как я уже говорил - другая история.
Итоги по протоколам:
Используются с class, struct, enum
Не хранят состояние
Могут быть унаследованы другими протоколами
Поддерживают множественное наследование
В расширениях можно описывать логику
Не указывают слабые ссылки
Не контролируют доступ без расширений
See you later...
kovserg
Казалось бы есть слово interface но зачем-то понадобилось его заменить на protocol.
Видимо не хотели избежать проблем так как слово interface начинается на букву i?
cbepxbeo Автор
Ну как сказать, быстрый поиск дает следующее определение:
Протокол — набор правил, соглашений, сигналов, сообщений и процедур, регламентирующий взаимодействие между сопрягаемыми объектами.
И вот такое определение:
Интерфейс – совокупность аппаратных и программных средств, необходимых для взаимодействия с программой, устройством, функцией.
Я понимаю, что Вы имели ввиду т.к. протоколы в Swift очень похожи на интерфейсы в Java, но все же склонен к тому, что interface менее подходящее определение, нежели protocol. Ну вот чисто логически. Интерфейс... даже само слово не дает мне конкретных ассоциации, я много чего могу себе представить, вплоть до визуальной части. А протокол, он и в Африке протокол. Сразу понятно, что это некая директива и т.д.
Конечно сколько людей, столько и мнений, но я попытался посмотреть на это сбоку. Возможно в Apple руководствовались тем же.
NeoCode
Вообще есть некоторый бардак в терминологии, в разных языках словами "интерфейс", "протокол", "трейт" и даже "миксин" называются разные вещи. Интерфейсы Go отличаются от таковых в C/C++/C#/Java, тем что в Go они структурно типизированы и реализуются неявно, а в большинстве языков - номинативно и требуется явная реализация. По "трейтам" вообще нет единого согласия, хотя то что в Swift больше на них похоже. Все это, вообще говоря, интересная тема для исследования.
cbepxbeo Автор
Я в последнее время, в основном, ориентируюсь на Swift, для меня это некий фаворит. Не с позиции слепой любви, много с чем работать приходилось, а вполне себе с мотивацией. Не уверен конечно, что мотивация претендует на звание «адекватной», но для меня ее хватает. Заключается она в следующем.
Компания Apple стоит у истоков компьютеростроения, у них единственных кроме Microsoft есть своя операционная система,а на мобильном поприще так они вообще многих похоронили. Проектируют процессоры, классный софт и т.д.. В общем я к чему это пишу, я полагаю там в обязательном порядке есть крутая команда Uber специалистов в разработке. Язык достаточно новый. Разрабатывала команда далеко не глупых людей. Если уж кто и учел современные тенденции, возможности и т.д. так это Apple в момент разработки языка. Пускай он молодой, но уже обладает и достаточной популярностью и возможностями. Возможности максимально логичные и продуманные. Не без косяков конечно, но это минусы обратной совместимости. Настанет время и Objective-C уйдет в прошлое, а Swift по настоящему раскроется.
Это конечно все демагогия, как уже писал выше сколько людей, столько и мнений. Но мое предпочтение на сегодня Swift и как Вы писали уже, терминология в Swift, по моему внутреннему компасу, отвечает всем требованиям. Было бы время, я бы с удовольствием и с головой погрузился бы в это (исследование), век живи, век учись так сказать, чтобы оперировать какими либо данными, а сейчас это чисто интуитивное ощущение.
Johan
На самом деле, ещё непонятно, кто кого и чем заменил.
Swift унаследовал термин протокол от Objective-C. Если верить Википедии, в Objective-C протоколы были добавлены в компании NeXT, которая просуществовала с 1985 по 1996 год (потом была куплена Apple). А первая версия Java вышла только в том самом 1996, опять же, если верить википедии.
Выходит, протоколы появились раньше чем интерфейсы и это инженерам Sun Microsystems зачем-то понадобилось заменить термин.
Другое дело, что Java захватила мир, а Objective-C так и остался нишевым. Но тут уж ничего не поделать.