Что такое ооп swift
Классы в Swift [Часть 1]
Недавно Apple представила общественности достаточно важное изменение в разработке iOS приложений, анонсировав новый язык программирования Swift. В настоящее время, количество материалов на русском, посвящённых этому языку, ограничено. Также Swift — язык объектно-ориентированный, и классы в нём — основа основ. Поэтому я решил перевести эту статью.
Создание классов
Инициализаторы
Инициализация — процесс подготовки экземпляра класса, структуры или перечисления для дальнейшего использования. Он включает в себя инициализацию каждого свойства и выполнение других настроек или инициализаций, необходимых до первого использования.
Процесс инициализации реализуется с помощью инициализаторов, которые являются специальными методами, вызывающимися при объявлении новых экземпляров класса. В отличие от Objective-C, в Swift инициализаторы ничего не возвращают, т.к. они обеспечивают правильность инициализации новых экземпляров класса.
Обратимся к примеру
Запустив его, вы получите три ошибки компиляции
Чтобы исправить ситуацию, давайте зададим тип каждого свойства
Перегрузка инициализаторов
Ну что ж, класс мы написали. Теперь пришло время создать первых пользователей. Поле краткой информации — дополнительное, значит нам нужно перегрузить инициализатор, т.к. можно как написать что-то про игрока, так и оставить его тёмной личностью. Ну что ж, давайте напишем инициализаторы для обоих случаев:
Однако согласитесь, это не совсем рационально, ведь отличия в инициализации минимальные — про кого-то есть какая-то информация, а про кого-то — нет. Поэтому давайте напишем один инициализатор, а аргументу bio дадим значение по умолчанию.
Идентичность классов
Операторы идентичности
Поскольку класс является ссылочным типом, может возникнуть ситуация, когда один и тот же экземпляр класса обозначается несколькими переменными или константами. (Это невозможно для структур и перечислений, т.к. и те, и другие являются значимыми типами и копируются при присваивании и передаче в функцию в качестве параметров)
Предположим, что у нас есть список игроков, один из них — владелец замка. Нам необходимо добавить какие-то функции для игроков, не являющихся владельцами замка. Для этого давайте используем оператор идентичности ( === )
Оператор равенства ( == ) не работает при сравнении объектов классов, так что вам не следует бояться подобных ошибок.
В следующей части мы рассмотрим наследование классов и протоколы.
Swift объектно-ориентированное программирование
В продолжении обучения переходим на новый уровень —
объектно — ориентированное программирование.
Формат обучения: онлайн + домашние задания
Курс подойдет студентам
Преподаватель и куратор курса:
💻 78 видео уроков
Вы их изучаете в удобном для себя темпе.
📄 Домашние задания
После прохождения каждой темы выполняйте домашние задания.
⚡ Базовые знания
После успешного прохождения этого курса вы получаете базовые знания в Swift
Чему вы научитесь на курсе?
Использовать основные принципы разработки в Swift
Вы будете готовы приступить к изучению разработке приложений
Продолжение курса Swift с нуля.
Большинство инфраструктур Apple имеют объектно-ориентированную архитектуру. Прежде чем приступить к разработке iOS / MacOS, вы должны сначала понять объектно-ориентированное программирование и шаблоны проектирования. В этом курсе мы рассмотрим основные понятия и шаблоны проектирования, чтобы вы начали разработку приложений.
Объектно-ориентированное программирование — это фундаментальная парадигма программирования, которую вы должны освоить, если серьезно относитесь к изучению Swft. Объектно-ориентированное программирование лежит в основе большинства фреймворков, с которыми вы будете работать.
Объекты могут использоваться для моделирования почти чего угодно — координат на карте, касания экрана, даже колебания процентных ставок на банковском счете и тд.
На замену Objective-C пришел Swift в приложениях для iOS устройств и Mac OS X. На него оказали влияние такие языки как Python, Ruby, Haskel и конечно же Objective-C.Apple описала язык Swift как “Objective-C без С”. Язык действительно получился удобным, мощным и продуманным.
В продолжении обучения «Swift c нуля» переходим на новый уровень — объектно-ориентированное программирование. Вы познакомитесь с основными концепциями объектно — ориентированного программирования на примере языка Swift. Будут рассмотрены классы и структуры, инкапсуляция, наследование, полиморфизм, инициализация и деинициализация, ARC, дженерики, расширения и протоколы и конечно обработка ошибок.
Общая продолжительность курса: 9 часов 21 минута
Объектно-ориентированное программирование в Swift
Объектно-ориентированное программирование в Swift
Разбираясь в проблеме взаимодействия между объектами с помощью сообщений, которыми объекты обмениваются, может привести сперва к непониманию, но это проверенный способ построения сложных систем, который берет свое начало еще с 1950-х
Почти в любой модели мы можем использовать объекты, для примера возьмем – координаты на карте, касания экрана, даже изменения процентных ставок на банковском счете. На старте изучения ООП, было бы здорово, если вы попробовали бы представить моделирование физических вещей в реальном мире, прежде чем применить их это на более абстрактных понятиях. Ведь почти все можно представить в виде объектов.
В этом уроке мы будем использовать объектно-ориентированное программирование для того, чтобы создать свою собственную модель объектов, для нашего примера возьмем группу музыкальных инструментов. Также в этой статье вы узнаете очень много важных концепций, включая:
— Переопределение и перегрузка методов
По содержанию видно, что придется изучить многое, но уверяю, что будет интересно, так что запаситесь временем, кружкой кофе и давайте начнем! :]
Поехали!
Запустите Xcode и перейдите в: File \ New \ Playground. Введите название для проекта: «Instruments», затем выберите платформу «iOS» и нажмите «Далее». Выберите место для хранения вашего проекта и нажмите кнопку «Create». Когда откроется Playground, с вашим новым проектом, очистите его полностью, чтобы лишняя информация вам не мешала
Проектирование объектов объектно-ориентированным способом обычно начинается с самой концепции построения, затем распространяется уже на конкретные типы. Вы хотите создавать музыкальные инструменты, поэтому имеет смысл начинать с типа Instrument (тип Instrument будет общий для всех), а затем определять конкретные (не буквально!) инструменты, такие как пианино и гитары. Подумайте об этом как о генеалогическом дереве инструментов, где все течет от общего типа к конкретному сверху вниз. Пример:
Заметка
Связь между дочерним типом и его родительским типом является отношением is-a. То есть отношением объектов друг к другу, через наследование. Например, мы видим, что «Guitar is-a Instrument». То есть «Instrument» – это родитель, а «Guitar» по отношению к типу «Instrument» является дочерним типом – Гитара является наследником типа Инструмент. Теперь, когда у вас есть визуальное представление объектов, с которыми мы будем работать, можно начать реализацию нашего проекта.
Свойства
Добавьте в Playground следующий блок кода:
Теперь давайте разберем, что мы написали выше:
Сейчас вы только что реализовали класс для будущих инструментов, который содержит одно свойство brand, но еще не задали для него поведение. Поведение – означает, что мы можем повлиять на состояние свойства (объекта), через реализацию метода(ов). Давайте добавим какое-нибудь поведение в виде методов для наших свойств.
Методы
Как уже и говорилось, с помощью методов вы можете задать поведение свойствам, например сделать метод настройки (tune() ) или попросить сыграть на инструменте (play() ), при этом независимо от конкретного типа свойств. Добавьте следующий код внутри класса Instrument сразу после инициализатора:
Такого рода реализация носит название «Инкапсуляция». Тип класса описывает инкапсулированные данные (например, свойства хранения) и их поведение (например, методы).
Теперь давайте добавим следующий код, но только уже перед нашим классом Instrument:
Это класс Music, который инкапсулирует массив нашего свойства notes с типом String и позволяет задать поведение, в нашем примере мы добавили поведение, которое может соединить все приходящие данные в свойстве, в одну строку, это метод prepared().
Теперь добавьте следующий метод в класс Instrument сразу после метода tune():
Метод play(_ : ) возвращает строку (String), музыку которую нужно воспроизвести. Возможно у вас возник вопрос, зачем нам создавать специальный тип Music, а не просто передать весь массив строк (String). Дело в том, что это дает ряд преимуществ: создание типа Music помогает создавать словарь, что позволит компилятору проверить корректность работы и создаст место для будущих расширений.
Теперь добавьте следующий метод в класс Instrument сразу после метода play(_ : ):
Метод perform(_ : ) задает поведение настройки инструмента, а затем воспроизводит музыку. Мы создали два метода рядом, чтобы мы могли насладиться настроенной, красиво звучащей симфонией. :]
Это все касалось реализации класса Instrument. Сейчас самое время добавить уже конкретные инструменты.
Наследование
Добавьте следующий класс в нижней части Playground, сразу после класса Instrument:
Давайте разберем, шаг за шагом:
Поскольку класс Piano наследуется от класса Instrument, он уже совершенно точно знает о классе Instrument, что: у него есть свойство brand, есть метод для настройки музыки tune(), есть метод проигрывания музыки play() и метод выполняющий воспроизведение музыки с нашими настройками perform().
Заметка
В классах Swift используется процесс инициализации, называемый двухфазной инициализацией, чтобы дать гарантию, что все свойства будут инициализированы до их использования. Если вы хотите узнать больше об инициализации, ознакомьтесь с нашей учебной серией по инициализации Swift.
Конечно вся музыка при игре на пианино издает какие либо звуки, но ведь звучать оно может по-разному. Поэтому пришло время добавить в вашу музыку педали, чтобы звучание можно было менять по нашему вкусу. :]
Перегрузка методов
Добавьте следующий метод в класс Piano, сразу после переопределенного метода play(_ : ):
Добавление этого метода перегружает метод play(_ : ) для задействования педалей, при условии, что usePedals у нас true, и у пианино доступны педали в параметрах. Мы не задействовали ключевое слово override, потому что у нового созданного метода play() отличается список параметров, в отличии от переопределенного метода play(). Swift использует список параметров (сигнатуру), чтобы автоматически определить, требуется ли переиспользование метода. Следует быть осторожными с перегрузкой методов, потому что они могут вас запутать. Например, метод perform(_ 🙂 всегда вызывает play (_ 🙂 и только его, он никогда не вызовет наш второй созданный метод play(_: usingPedals 🙂.
Сделайте в методе play(_ : ), в классе Piano, другую реализацию метода, которая будет вызывать другую педаль, добавьте код ниже:
Теперь пришло время попробовать с созданием экземпляров класса Piano, мы сможем сделать для него необходимые настройки и воспроизвести на нем действительно классную музыку. :]
Экземпляры
Добавьте следующий блок кода в конце Playground сразу после объявления класса Piano:
Теперь разберемся, что у нас тут происходит:
Теперь, когда у вас получилось наиграть мелодию на пианино, пришло время добавить гитарное соло в вашу музыку.
Промежуточный абстрактный базовый класс
Добавьте класс Guitar в самом конце вашего Playground:
Мы создали новый класс Guitar, который вносит идею струнного датчика в виде текстового параметра строки (String) для базового класса Instrument. Подобно классу Instrument, подкласс Guitar считается абстрактным типом, методы tune () и play (_ : ) должны быть переопределены в подклассе Guitar. Поэтому его иногда называют промежуточным абстрактным базовым классом.
Заметка
Вы заметите, что нет ничего, что помешало бы вам создать экземпляр абстрактного класса. Это так, и это ограничение для Swift. Некоторые языки позволяют вам конкретно указать, что класс является абстрактным и что вы не можете создать его экземпляр.
Теперь для классов, которые будут наследовать тип Guitar, вы сможете добавить действительно крутые гитарные партии! Давай сделаем это! :]
Конкретные гитары
Все акустические гитары имеют 6 струн и 20 ладов, нам потребуется определить необходимые для этого свойства и определим их как static, поскольку они будут относиться ко всем акустическим гитарам. Static означает, что они являются константами, их значение никогда не изменится, ранее об этом мы уже говорили. В классе отсутствуют свойства, которым потребуется инициализация, поэтому создавать инициализатор нам не потребуется. Класс AcousticGuitar по умолчанию наследует инициализатор от своего родительского класса Guitar.
Итак, время испытать нашу гитару!
Задачка
Нужно определить акустическую гитару Roland. Настроить ее и сыграть на ней.
Решение ниже!
Добавьте следующий код в нижней части Playground сразу после объявления класса AcousticGuitar:
Пришло время немного пошуметь и сыграть какую-нибудь громкую музыку. Теперь уже нам потребуется усилитель, так что создадим его! :]
Уровень доступа Private
Акустические гитары играют превосходно, но с усилителем они будут играть гораздо лучше. Добавьте класс Amplifier в нижней части Playground, чтобы начать нашу вечеринку:
Здесь довольно много что у нас происходит, поэтому давайте разберёмся:
Композиции
Теперь, когда у вас есть удобный компонент усилителя, пришло время использовать его в электрогитаре. Добавьте класс ElectricGuitar в конце Playground сразу после класса Amplifier:
Аналогичным образом добавьте класс BassGuitar в нижней части Playground сразу после класса ElectricGuitar:
Мы только что создали бас-гитару, которая также использует has-a по отношению к усилителю. Все этот класс завершили. Переходим к задаче ниже!
Задачка
Возможно, вы слышали, что классы следуют ссылочной семантике (reference semantics). Это означает, что переменные, содержащие экземпляр класса, фактически содержат ссылку на этот экземпляр. Если у вас есть две переменные с одной и той же ссылкой, изменение данных в одном изменит данные в другом, и на самом деле это одно и то же. Давайте посмотрим на ссылочную семантику в действии, создав экземпляр усилителя и разделив его между электрогитарой “Gibson” и бас-гитарой “Fender”.
Добавьте следующий код в нижней части Playground сразу после объявления класса BassGuitar:
Полиморфизм
Одной из сильных сторон объектно-ориентированного программирования является возможность использовать разные объекты через один и тот же интерфейс, в то время когда каждый реализовывает свою логику. Это полиморфизм, означающий «many forms» (много-форменный). Добавьте класс Band в Playground:
Класс Band имеет свойство массива instruments, которую мы определили в инициализаторе. Группа (band) выступает вживую на сцене, проходя через массив (array) инструментов (instruments) в цикле for in и вызывает метод perform(_ 🙂 для каждого инструмента в массиве.
Теперь давайте продолжим и подготовим первый свой рок-концерт. Добавьте следующий блок кода в нижней Playground, сразу после класса Band:
Сначала вы определяете массив instruments (инструментов) из экземпляров класса Instrument, которые ранее мы создали. Затем вы объявляете объект band и настраиваете свойство своих instruments (инструментов) с помощью инициализации в классе Band. Теперь, вы используете метод perform(_ : ), экземпляра band,
чтобы задавать поведение группе и исполнять живую музыку (в Playground вы должны увидеть результат вашей музыки).
Модификаторы доступа
Вы уже видели private (приватный доступ) в действии, как один из способов скрыть внутреннюю реализацию и защитить свои классы от нежелательных изменений. Помимо private доступа Swift есть еще четыре уровня доступа, это:
Private: Доступен только внутри класса.
Fileprivate: Доступен из любого места в пределах одного файла.
internal: Доступен из любого места в том же модуле или приложении.
Public: Доступен вне модуля.
Дополнительно существуют еще два доступа:
Open: Может быть доступен не только вне модуля, но также может быть унаследован и переопределен.
Final: Невозможно переопределить или сделать какие либо изменения
Если вы не укажете модификатор доступа к классу, свойству или методу, по умолчанию будет использован internal доступ. Сегодня мы использовали достаточно простой проект и по большому счету модификаторы доступа тут не важны. Но об этом стоит задуматься всерьез на будущее, когда ваше приложение будет становиться все больше и сложнее, вам обязательно потребуется использование модификаторов доступа, чтобы скрывать часть реализации.
Создание фрэимворка
Предположим, вы захотели создать свой собственный музыкальный и инструментальный фрэимворк. Вы можете сделать это, добавив новый класс в папку «Sources» в скомпилированные проекте вашего Playground. Во-первых, удалите классы Music и Instrument из Playground. Это вызовет множество ошибок, которые вы сейчас исправите ниже.
Убедитесь, что Project Navigator (Навигатор проектов) доступен в Xcode, перейдя в меню View\Navigators\Show Project Navigator. Затем щелкните правой кнопкой мыши папку «Sources» и выберите «New File» в меню. Переименуйте файл в MusicKit.swift и удалите внутри все лишнее. И вставьте указанный код ниже:
Сохраните файл и вернитесь на главную страницу Playground. Не беспокойтесь, ваш проект будет работать как и прежде.
Итак, что мы здесь сделали:
А что дальше?
Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!
Протокольно-ориентированное программирование в Swift 5.1
Протоколы — фундаментальное свойство Swift. Они играют важную роль в стандартных библиотеках Swift и являются обычным способом абстракции кода. Во многом они похожи на интерфейсы в других языках программирования.
В этом руководстве мы представим вам подход к разработке приложений, называемый «протокольно-ориентированным программированием», который стал практически основным в Swift. Это действительно то, что вам необходимо уяснить при изучении Swift!
В этом руководстве вы:
Начинаем
Представьте, что вы разрабатываете игру — гонки. Ваши игроки могут гонять на машинах, мотоциклах и на самолётах. И даже летать на птицах, это же ведь игра, верно? Основное здесь то, что есть дофига всяких «штук», на которых можно гонять, летать и т.п.
Обычный подход при разработке подобных приложений состоит в объектно-ориентированном программировании. В этом случае мы заключаем всю логику в неких базовых классах, от которых в дальнейшем наследуемся. Базовые классы, таким образом, должны содержать внутри логику «вести» и «пилотировать».
Мы начинаем разработку с создания классов для каждого средства передвижения. «Птиц» отложим пока на потом, к ним мы вернёмся чуть позже.
Мы видим, что Car и Motorcycle весьма похожи по функционалу, так что мы создаём базовый класс MotorVehicle. Car и Motorcycle будут наследоваться из MotorVehicle. Таким же образом мы создаем базовый класс Aircraft, от которого создадим класс Plane.
Вы думаете, что всё прекрасно, но — бац! — действие вашей игры происходит в XXX столетии, и некоторые машины уже могут летать.
Итак, у нас случился затык. У Swift нет множественного наследования. Каким образом ваши летающие автомобили смогут наследоваться и от MotorVehicle и от Aircraft? Создавать еще один базовый класс, в котором соединятся обе функциональности? Скорее всего, нет, так как нет ясного и простого способа добиться этого.
И что же спасёт нашу игру в этой ужасной ситуации? На помощь спешит протокольно-ориентированное программирование!
Что такого в протокольно-ориентированном программировании?
Протоколы позволяют группировать похожие методы, функции и свойства применительно к классам, структурам и перечислениям. При этом только классы позволяют использовать наследование от базового класса.
Преимущество протоколов в Swift состоит в том, что объект может соответствовать нескольким протоколам.
Ваш код при при использовании такого метода становиться более модульным. Думайте о протоколах как о строительных блоках функционала. Когда вы добавляете новую функциональность объекту, делая его соответствующим некоему протоколу, вы не делаете совершенно новый объект «с нуля», это было бы слишком долго. Вместо этого, вы добавляете разные строительные блоки до тех пор, пока объект не будет готов.
Переход от базового класса к протоколам решит нашу проблему. С протоколами мы можем создать класс FlyingCar, который соответствует и MotorVehicle и Aircraft. Миленько, да?
Займёмся кодом
Запускаем Xcode, создаём playground, сохраняем как SwiftProtocols.playground, добавляем этот код:
Скомпилируем при помощи Command-Shift-Return, чтобы быть уверенным, что все в порядке.
Здесь мы определяем простой протокол Bird, со свойствами name и canFly. Затем определяем протокол Flyable со свойством airspeedVelocity.
В «допротокольную эпоху» разработчик начал бы с класса Flyable в качестве базового, а затем, используя наследование, определил бы Bird и всё прочее, что может летать.
Но в протокольно-ориентированном программировании всё начинается с протокола. Эта техника позволяет нам инкапсулировать набросок функционала без базового класса.
Как вы сейчас увидите, это делает процесс проектирования типов гораздо гибче.
Определяем тип, соответствующий протоколу
Добавьте этот код внизу playground:
Этот код определяет новую структуру FlappyBird, которая соответствует и протоколу Bird и протоколу Flyable. Её свойство airspeedVelocity — произведение flappyFrequency and flappyAmplitude. Свойство canFly возвращает true.
Теперь добавьте определения еще двух структур:
Penguin это птица, но не может летать. Хорошо, что мы не пользуемся наследованием и не сделали всех птиц Flyable!
При использовании протоколов вы определяете компоненты функционала и делаете все подходящие объекты соответствующими протоколу
Затем мы определяем SwiftBird, но в нашей игре есть несколько разных её версий. Чем больше номер версии, тем больше её airspeedVelocity, которая определена как вычисляемое свойство.
Однако, здесь есть некоторая избыточность. Каждый тип Bird должен определить явно определить свойство canFly, хотя у нас есть определение протокола Flyable. Похоже, что нам нужен способ определить реализацию методов протокола по умолчанию. Что ж, для этого существуют расширения протоколов (protocol extensions).
Расширяем протокол поведением по умолчанию
Расширения протокола позволяют задать поведение протокола по умолчанию. Напишите этот код сразу за определением протокола Bird:
Этот код определяет расширение протокола Bird. В этом расширении определяется, что свойство canFly вернёт true в случае, когда тип соответствует протоколу Flyable. Другими словам, всякой Flyable-птице больше не нужно явно задавать canFly.
Займёмся перечислениями
Перечисления в Swift могут соответствовать протоколам. Добавьте следующее определение перечисления:
Определяя соответствующие свойства, UnladenSwallow соответствует двум протоколам — Bird и Flyable. Такими образом, реализуется определение по умолчанию для canFly.
Переопределяем поведение по умолчанию
Наш тип UnladenSwallow, соответствуя протоколу Bird, автоматически получил реализацию для canFly. Нам, однако, нужно, чтобы UnladenSwallow.unknown возвращала false для canFly.
Добавьте внизу следующий код:
Скомпилируйте playground и проверьте полученные значения с указанными в комментариях выше.
Таким образом мы переопределяем свойства и методы почти так же, как используя виртуальные методы в объектно-ориентированном программировании.
Расширяем протокол
Вы также можете сделать свой собственный протокол соответствующим другому протоколу из стандартной библиотеки Swift и определить поведение по умолчанию. Замените объявление протокола Bird следующим кодом:
Соответствие протоколу CustomStringConvertible означает, что у вашего типа должно быть свойство description. Вместо того, чтобы добавлять это свойство в типе Bird и во всех производных от него, мы определяем расширение протокола CustomStringConvertible, которое будет ассоциировано только с типом Bird.
Наберите UnladenSwallow.african внизу playground. Скомпилируйте и вы увидите “I can fly”.
Протоколы в стандартных библиотеках Swift
Как видите, протоколы — эффективный способ расширять и настраивать типы. В стандартной библиотеке Swift это их свойство также широко применяется.
Добавьте этот код в playground:
Вы наверняка знаете, что выведет этот код, но, возможно, удивитесь использованным здесь типам.
Например, slice — не Array, а ArraySlice. Это специальная «обёртка», которая обеспечивает эффективный способ работы с частями массива. Соответственно, reversedSlice — это ReversedCollection.
К счастью, функция map определена как расширение к протоколу Sequence, которому соответствуют все типы-коллекции. Это позволяет нам применять функцию map как к Array, так и к ReversedCollection и не замечать разницы. Скоро вы воспользуетесь этим полезным приёмом.
На старт
Пока что мы определили несколько типов, соответствующих протоколу Bird. Сейчас же мы добавим нечто совсем другое:
У этого типа нет ничего общего с птицами и полётами. Мы хотим устроить гонку мотоциклистов с пингвинами. Пора выводить эту странную компашку на старт.
Соединяем всё вместе
Чтобы как-то объединить столь разных гонщиков, нам нужен общий протокол для гонок. Мы сможем все это сделать, даже не трогая все созданные нами до этого типы, при помощи замечательной вещи, которая называется ретроактивное моделирование. Просто добавьте это в playground:
Вот что мы тут делаем: сначала определяем протокол Racer. Это всё то, что может участвовать в гонках. Затем мы приводим все наши созданные до этого типы к протоколу Racer. И, наконец, мы создаём Array, который содержит в себе экземпляры каждого нашего типа.
Скомпилируйте playground, чтобы все было в порядке.
Максимальная скорость
Напишем функцию для определения максимальной скорости гонщиков. Добавьте этот код в конце playground:
Здесь мы используем функцию max чтобы найти гонщика с максимальной скоростью и возвращаем её. Если массив пуст, то возвращается 0.0.
Делаем функцию более обобщенной
Предположим, что массив Racers достаточно велик, а нам нужно найти максимальную скорость не во всем массиве, а в какой-то его части. Решение состоит в том, чтобы изменить topSpeed(of:) таким образом, чтобы она принимала в качестве аргумента не конкретно массив, а всё, что соответствует протоколу Sequence.
Заменим нашу реализацию topSpeed(of:) следующим образом:
Теперь наша функция работает с любым типом, отвечающим протоколу Sequence, в том числе и с ArraySlice.
Делаем функцию более «свифтовой»
По секрету: можно сделать ещё лучше. Добавим это в самом низу:
А вот теперь мы расширили сам протокол Sequence функцией topSpeed(). Она применима только в случае, когда Sequence содержит тип Racer.
Компараторы протоколов
Другая особенность протоколов Swift — это то, как вы определяете операторы равенства объектов или их сравнения. Напишем следующее:
Имея протокол Score можно писать код, который обращается со всеми элементами этого типа одним образом. Но если завести вполне определенный тип, такой как RacingScore, то вы не спутаете его с другими производными от протокола Score.
Мы хотим, чтобы очки (scores) можно было сравнивать, чтобы понять, у кого максимальный результат. До Swift 3 разработчикам было нужно писать глобальные функции для определения оператора к протоколу. Теперь же мы можем определить эти статические методы в самой модели. Сделаем это, заменив определения Score и RacingScore следующим образом:
Мы заключили всю логику для RacingScore в одном месте. Протокол Comparable требует опеределить реализацию только для функции «меньше, чем». Все остальные функции сравнения будут реализованы автоматически, на основании созданной нами реализации функции «меньше, чем».
Вносим изменения в объект
До сих пор каждый пример демонстрировал, как добавить функционал. Но что, если мы хотим сделать протокол, который что-то изменяет в объекте? Это можно сделать при помощи mutating методов в нашем протоколе.
Добавьте новый протокол:
Здесь мы определяем протокол, который дает нам возможность жульничать (cheat). Каким образом? Произвольно изменяя содержимое boost.
Теперь создадим расширение SwiftBird, которое соответствует протоколу Cheat:
Здесь мы реализуем функцию boost(_:), увеличивая speedFactor на передаваемую величину. Ключевое слово mutating даёт структуре понять, что одно из её значений будет изменено этой функцией.
Заключение
Здесь вы можете загрузить полный исходный код playground.
Вы узнали о возможностях протокольно-ориентированного программирования, создавая простые протоколы и увеличивая их возможности их при помощи расширений. Используя реализацию по умолчанию, вы даете протоколам соответсвующее «поведение». Почти как с базовыми классами, но только лучше, так как все это применимо также к структурам и перечислениям.
Вы также увидели, что расширение протоколов применимо и к базовым протоколам Swift.
Вы можете также посмотреть прекрасную лекцию на WWDC, посвященную протокольно-ориентированному программированию.
Как и с любой парадигмой программирования, есть опасность увлечься и начать использовать протоколы налево и направо. Здесь интересная заметка о том, что стоит опасаться решений в стиле «серебряная пуля».