Привет.
В этом видео мы начнём разбирать большую и очень важную тему в Swift — протоколы.
Также мы затронем эту тему при обсуждении дженериков,
расширений классов и наследований.
Протокол — это перечисление свойств, методов и других требований.
Они могут быть выполнены в классе, структуре или перечислении.
Говорят, что тип поддерживает протокол,
если он предоставляет реализацию всех требований.
Обратите внимание на то, что в самом протоколе ничего не объявлено,
это просто список требований.
Все свойства и методы должны быть реализованы непосредственно в типе,
который поддерживает протокол.
Чтобы объявить новый протокол, нужно использовать ключевое слово protocol.
После него указывается имя с большой буквы и в фигурных скобках требования.
Объявление очень похоже на объявление структуры или класса.
[БЕЗ_ЗВУКА] В
данном примере мы создали протокол без требований с именем MyProtocol.
Давайте теперь создадим структуру, поддерживающую этот протокол.
Для этого нужно указать его при объявлении структуры после двоеточия.
Таким образом, можно перечислить через запятую несколько протоколов.
Объявим более полезный протокол, добавим в него несколько требований.
Для этого нужно их указать так же, как при объявлении в структуре или классе.
После типа в фигурных скобках указывается,
может ли это свойство быть только для чтения или нужно дать доступ и
на установку значений при помощи ключевых слов get set.
Добавим новую структуру,
в ней объявим пару свойств для поддержки данного протокола.
[БЕЗ_ЗВУКА] Протокол
не может требовать вычисляемое или обычное свойство,
поэтому мы в структуре можем использовать и то, и другое.
Обратите внимание на то, что можно определить setter для свойства,
даже если в протоколе указано, что его нам необходимо будет только получать.
В протокол можно добавить требования не только для экземпляра,
но и для самих типов.
Для этого нужно добавить ключевое слово static перед свойством.
[БЕЗ_ЗВУКА] А
теперь объявим протокол с требованием к методам.
[БЕЗ_ЗВУКА] Они похожи на
объявление методов в структурах и классах, но без фигурных скобок и реализации.
Также можно использовать слово static для методов типов,
но значение по умолчанию указывать нельзя.
Добавим структуры с поддержкой сразу нескольких протоколов.
[БЕЗ_ЗВУКА] Реализация метода может быть любой,
протокол требует только наличие самого метода, но не логики в нём.
Так как мы добавили mutating при объявлении метода в протоколе,
мы можем изменять значение свойств в данном методе.
Помимо обычных методов протокол может требовать наличие инициализатора.
[БЕЗ_ЗВУКА] Делается это таким же образом,
как и объявление инициализатора в типе, только без имплементации.
Однако есть одна особенность.
В классе, поддерживающем этот протокол,
нужно обязательно объявить такой инициализатор, как required.
[БЕЗ_ЗВУКА] Это нужно для того,
чтобы все наследники тоже предоставили собственную реализацию.
Подробнее о наследовании и инициализаторах смотрите в соответствующих лекциях.
Несмотря на то, что протоколы не включают в себя никакую имплементацию,
а только требования,
мы всё равно можем использовать их как любой другой тип в Swift.
Можно объявить переменную или константу этого типа,
можно принимать и возвращать их из функции и так далее.
В данном
примере мы присваиваем экземпляр структуры
MyStructWithProperties переменной, которая объявлена как протокол.
Вызывающая сторона может обратиться только к тем свойствам и методам,
которые есть в требованиях протокола.
Мы можем присвоить этой переменной экземпляр другой структуры.
При этом для вызывающей стороны ничего не изменится.
Известно лишь то,
что у этого объекта будут свойства gettableProperty и settableProperty.
Это один из вариантов использования протоколов, он часто встречается в
разработке [НЕРАЗБОРЧИВО] неразборчиво в виде архитектурного паттерна delegate.
Реализуем пример delegate.
Для начала опишем класс, который будет обрабатывать массив строк.
У него будет хранящееся свойство delegate.
В методе process он каждую строку трансформирует при помощи delegate и
складывает результат в одну строку, используя разделитель.
В требованиях в протоколе укажем метод для трансформации и разделитель в
виде свойства.
А теперь объявим несколько классов, поддерживающих этот протокол.
Первый будет трансформировать все строки в верхний регистр.
Разделителем будет пробел.
А второй возвращает из трансформации только первый символ в строке.
Разделителем у него будет являться пустая строка.
Попробуем всё в действии.
Создадим процессор, его delegate и тестовый массив.
[БЕЗ_ЗВУКА] Сначала
просто вызовем обработку без delegate.
Получим ожидаемую пустую строку.
Выставим первый delegate в процесс.
В результате получим строку, состоящую из заглавных букв.
[БЕЗ_ЗВУКА]
[БЕЗ_ЗВУКА] Попробуем то же самое со вторым delegate.
Получим строку, составляющую из первых букв всех тестовых слов.
[БЕЗ_ЗВУКА] Как видите,
мы вынесли логику из процессоров delegate.
Это дает нам возможность легко изменять поведение,
просто поменяв один delegate на другой.
Со стороны процессора при этом ничего не меняется, он знает только,
что его delegate будет предоставлять ему методы, перечисленные в протоколе.
С ними он и взаимодействует.
Помимо простой переменной мы можем объявить и целую коллекцию объектов,
поддерживающих какой-то протокол.
Создадим массив delegate процессора.
Теперь мы можем поместить в него любой объект, поддерживающий протокол
StringProcessorDelegate.
Тип объектов будет соответствовать протоколу.
Вы не будете знать, что за объект вы получили из массива.
Конечно, в Swift есть приведение типов, но если у вас при написании кода возникает
такая необходимость, значит, что-то вы делаете не так.
Apple называет Swift протоколо-ориентированным языком
программирования.
Протокол — это не просто абстрактный интерфейс класса или структуры.
Добавляя поддержку протокола в какой-либо тип,
мы добавляем ему определенную функциональность.
Например, equatable для проверки на равенство,
[НЕРАЗБОРЧИВО] для инициализации типа при помощи целочисленного литерала,
hashable для получения хэш суммы и так далее.
Объявляя какой-нибудь метод, мы просто перечисляем,
поддержку какой функциональности мы ждём от получаемого объекта.
При этом нам уже неважен его тип.
В лекции по расширению функциональности мы покажем,
как добавить поддержку протокола уже существующему типу.
А пока посмотрим, как использовать композицию протоколов.
Композиция — это объединение нескольких протоколов в один тип.
Их еще называют экзистенциальными типами.
Объявляются они перечислением протоколов через амперсанд.
[БЕЗ_ЗВУКА] В
константу CompoundObject мы можем поместить только
те структуры, классы или перечисления,
которые поддерживают, как протокол MyProtocolWithProperties неразборчиво,
так и протокол MyProtocolWithMethods неразборчиво.
В Swift 4 добавили возможность объявить экзистенциальный тип,
содержащий не только протоколы, но и класс.
Это может очень пригодиться при работе с системными классами.
В следующем примере мы объявляем переменную с таким типом.
[БЕЗ_ЗВУКА] В
данную константу может быть помещен только тот объект, который является
классом UIView или его наследником, а также поддерживает протокол MyProtocol.
Вы еще встретите подобные конструкции в следующих лекциях, и будет понятнее,
как это всё использовать и зачем.
Мы можем проверить, поддерживает ли объект какой-то протокол или привести, например,
enit object к нужному протоколу.
Делается это так же, как и с любым другим типом с помощью операторов is, s!
и s?.
[БЕЗ_ЗВУКА] Рассмотрим простой пример.
У нас есть массив объектов, которые по какой-то причине имеют тип массив,
который может содержать абсолютно любые значения.
Но мы знаем, что в нём должны быть объекты,
которые поддерживают какой-то протокол.
Воспользуемся ключевыми словами s?
и flat для безопасного преобразования типа.
Даже если у вас в массив попало что-то другое, как в нашем примере,
то ничего страшного не произойдёт.
Не стоит использовать s!
[НЕРАЗБОРЧИВО], аже если уверены, что ничего лишнего вам прийти не может.
В Swift все требования протокола обязательны к исполнению.
Если вы хотите иметь возможность использовать опционально протоколы,
как в Objective-C, то вам придётся пользоваться атрибутом obj-c.
Он был добавлен в Swift для обеспечения взаимодействия с Objective-C.
Объявим протокол, добавим к нему атрибут obj-c,
и теперь он будет доступен для Objective-C кода.
Все опциональные требования также должны быть помечены этим атрибутом.
[БЕЗ_ЗВУКА] Обратите внимание,
что опциональным стал не возвращаемый тип, а сам метод.
У опциональных протоколов есть ограничения.
Они могут поддерживаться только классами из Objective-C либо свифтовыми классами,
помеченными атрибутом obj-c.
При этом структуры и перечисления не могут поддержать такой протокол.
Объявим следующий класс.
[БЕЗ_ЗВУКА] Наследование
мы рассмотрим в будущих лекциях, но сейчас мы обязаны им воспользоваться.
Синтаксис наследования в Swift похож на другие языки программирования и,
скорее всего, покажется вам знакомым.
Мы объявляем класс, который является наследником ns object,
и добавляем ему поддержку нашего опционального протокола.
Ns object — это основной базовый класс в Objective-C.
От него наследуется большинство других классов.
Скорее всего, вы будете работать с ним либо с одним из его саб-классов.
Как видите, мы не добавили опциональный метод в наш класс.
При вызове нужно учитывать такую ситуацию.
Для этого укажем после имени метода ?.
В этом случае ничего вызываться не будет, и результат выполнения будет nil.
Обратите внимание, что тип переменной указан явно, является obj-c protocol.
Если бы переменная была MeObjcExposedClass, то Swift мог бы
определить, что нужный метод отсутствует и не дал бы скомпилировать код.
На этом наше видео о протоколах закончено.
Сегодня мы рассмотрели основы, как объявить протокол, как их лучше
использовать, и немного затронули тему взаимодействия Swift и Objective-C.
Но мы еще не раз вернемся к теме протоколов в будущих лекциях.