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, и как заместить им ООП.
Для простоты картины положим, что у каждого стэка есть переменная (stack pointer). Она используется для отслеживания вершины стэка и хранит в себе целочисленное число (Integer). Из этого следует, что скорость операций со stack-ом равна скорости перезаписи Integer-а в эту переменную.
Типы значений
Рассмотрим принципы работы стэка в Swift, используя структуры (struct).
В Swift типами значений являются структуры (struct) и перечисления (enum), а ссылочными типами являются классы (class) и функции/замыкания (func). Типы значений хранятся на Stack, ссылочные типы хранятся на Heap.
Ссылочные типы
Рассмотрим принцип работы Heap в Swift, используя классы.
В некоторых ситуациях выбор в пользу Stack не только упрощает работу с памятью, но и улучшает качество кода. Рассмотрим на примере:
Для чего?
Псевдокод
Рассмотрим небольшой фрагмент псевдокода для демонстрации работы подсчета ссылок:
Struct
При работе со структурами, такой механизм как reference counting просто не нужен:
Копирование ссылок
Снова, struct и любые другие типы значений в Swift копируются при присваивании. В случае, если структура хранит в себе ссылки, они также скопируются:
3.1 Method Dispatch
— это алгоритм, который ищет код метода, который был вызван
Не самый оптимальный, так как:
1. Корректная реализация метода будет определяться в момент выполнения программы, что требует ресурсов и времени
2. Ни о каких оптимизациях компилятора не может быть и речи
Тогда как работает Dynamic Dispatch? У каждого объекта есть поле type. Так Point(. ).type будет равен Point, а Line(. ).type будет равен Line. Также где-то в (статической) памяти программы расположена таблица (virtual-table), где для каждого type есть список с его реализациями методов.
В Objective-C поле type известно как поле isa. Оно присутствует у каждого Objective-C объекта (NSObject).
Метод класса хранится в virtual-table и не имеет ни малейшего понятия о self. Для того, чтобы использовать self внутри этого метода, его (self) нужно туда передать.
Таким образом, компилятор изменит этот код на:
Необходимо обращать внимание на такие мелочи как:
1. При создании инстанса: где он будет размещен?
2. При работе с этим инстансом: как будет работать подсчет ссылок?
3. При вызове метода: чем он будет обработан?
Если мы платим за динамичность, не осознавая этого и не имея в этом надобности, то это отрицательно скажется на выполняемой программе.
Классы в Swift [Часть 1]
Недавно Apple представила общественности достаточно важное изменение в разработке iOS приложений, анонсировав новый язык программирования Swift. В настоящее время, количество материалов на русском, посвящённых этому языку, ограничено. Также Swift — язык объектно-ориентированный, и классы в нём — основа основ. Поэтому я решил перевести эту статью.

Создание классов
Инициализаторы
Инициализация — процесс подготовки экземпляра класса, структуры или перечисления для дальнейшего использования. Он включает в себя инициализацию каждого свойства и выполнение других настроек или инициализаций, необходимых до первого использования.
Процесс инициализации реализуется с помощью инициализаторов, которые являются специальными методами, вызывающимися при объявлении новых экземпляров класса. В отличие от Objective-C, в Swift инициализаторы ничего не возвращают, т.к. они обеспечивают правильность инициализации новых экземпляров класса.
Обратимся к примеру
Запустив его, вы получите три ошибки компиляции
Чтобы исправить ситуацию, давайте зададим тип каждого свойства
Перегрузка инициализаторов
Ну что ж, класс мы написали. Теперь пришло время создать первых пользователей. Поле краткой информации — дополнительное, значит нам нужно перегрузить инициализатор, т.к. можно как написать что-то про игрока, так и оставить его тёмной личностью. Ну что ж, давайте напишем инициализаторы для обоих случаев:
Однако согласитесь, это не совсем рационально, ведь отличия в инициализации минимальные — про кого-то есть какая-то информация, а про кого-то — нет. Поэтому давайте напишем один инициализатор, а аргументу bio дадим значение по умолчанию.
Идентичность классов
Операторы идентичности
Поскольку класс является ссылочным типом, может возникнуть ситуация, когда один и тот же экземпляр класса обозначается несколькими переменными или константами. (Это невозможно для структур и перечислений, т.к. и те, и другие являются значимыми типами и копируются при присваивании и передаче в функцию в качестве параметров)
Предположим, что у нас есть список игроков, один из них — владелец замка. Нам необходимо добавить какие-то функции для игроков, не являющихся владельцами замка. Для этого давайте используем оператор идентичности ( === )
Оператор равенства ( == ) не работает при сравнении объектов классов, так что вам не следует бояться подобных ошибок.
В следующей части мы рассмотрим наследование классов и протоколы.
Проектирование классов на Swift
Содержание
Виды классов и их представление
Когда выбрать наследование, а когда композицию
Почему абстракции так важны
Наследовать не для повторного использования
Свойства класса должны быть закрытыми
Введение
В данной статье я хочу рассмотреть ключевые вопросы касательно проектирования классов на языке Swift и их особенности. Мы рассмотрим как это сделать правильно, как не допускать ошибки, избежать проблем и как правильно управлять зависимостями между объектами.
Виды классов и их представление
Существует большое количество различных видов классов и каждый вид служит для разных целей. При проектировании у вас всегда должно быть чёткое представление того, какую проблему должен решать новосозданный класс. Каждый вид следует определенным правилам. Разберем какие типы классов бывают.
Базовый класс. Такой вид классов должен представлять собой некий строительный блок для всей последующей иерархии классов. Базовый класс обладает следующими свойствами:
Обычно базовый класс представляет интерфейс посредством виртуальных функций, но язык Swift такой функциональностью не наделен, из-за этого разработчики часто приходят к »костыльной» реализации данного механизма путем написания assertionFailure в методе, который необходимо переопределить в дочернем классе или же оставляют его пустым. В случае если разработчик создаст экземпляр такого класса и вызывает метод, то он получит ошибку о том, что этот метод должен быть переопределен.
Объекты такого класса представляют семантику ссылочного типа и создаются динамически в куче как часть объекта производного класса и используются посредством указателей.
Классы стратегий. Шаблонные классы, являются фрагментами сменного поведения. Стратегия — это поведенческий паттерн, выносит набор алгоритмов в собственные классы и делает их взаимозаменимыми. Другие объекты содержат ссылку на объект-стратегию и делегируют ей работу. Во время выполнения, программа может подменить этот объект другим, если требуется иной способ решения задачи.
Кейс из реального проекта. Валидация данных обязательный процесс перед отправкой их на сервер, представим, что Presenter получает какой-то ввод от пользователя из View в метод didUserInputEnter(_ input: ) и валидирует данные используя алгоритм инкапсулируемый в классе стратегии, это может быть EmailValidator, а может NumericValidator. В зависимости от логики, Presenter может прямо в runtime подменить алгоритм валидации данных используя классы стратегии.
Класс-значение. Такие классы моделируют встроенный тип и должны обладать следующими свойствами:
Имеют присваивание с семантикой значения.
Не имеют виртуальных функций.
Предназначены для использования в качестве конкретных классов.
При передаче в метод в качестве параметра не копируются, а передаются посредствам указателя, до какой-либо модификации.
Реализуют механизм copy on write (COW).
Класс-свойство (Utility-класс или Helper-класс). Такого рода классы представляют собой шаблон-контейнер, который несет информацию о типе и его функциональности. Класс-свойство обладает следующими характеристиками:
Содержит только статические поля и методы.
Не имеет модифицируемого состояния.
Не имеет виртуальных функций.
Объекты данного класса не создаются (конструкторы приватные).
Основная проблема таких классов в том, что они не обладают состояниями, это просто пространство имен, где сгруппированы методы, которые принимают на вход какие-то параметры, что-то с ними делают и возвращают данные. Например:
Многие разработчики любят выносить утилитные методы в extensions к какому либо типу. Яркий тому пример класс Date:
Монолиты
Минимальный класс легче понять и проще использовать повторно.
Минимальный класс проще в употреблении. Монолитный класс часто должен использоваться как большое неделимое целое.
Монолитные классы снижают инкапсуляцию. Если класс имеет много функций-членов, которые не обязаны быть членами, но тем не менее являются таковыми (таким образом обеспечивается излишняя видимость закрытой реализации), то закрытые свойства класса становятся почти столь же плохими с точки зрения дизайна, как и открытые переменные.
Монолитные классы обычно являются результатом попыток предсказать и предоставить «полное» решение некоторой проблемы.
У нас есть датчик, который измеряет температуру в помещении с каким-то интервалом. Для взаимодействия с ним мы разработали класс DateTemperature, который имеет поля и методы необходимые для работы со временем и температурой. На следующий день к нам приходит наш член команды и хочет воспользоваться DateTemperature для работы со временем, но он замечает, что в нашей реализации слишком много лишнего и принимает решение написать свой собственный класс DateTime и перенести с DateTemperature все необходимые методы в него, аналогично с температурой. После всех манипуляций мы сдаем наши классы в тестирование и как это обычно бывает, там находят кучу багов. Вносим изменения в DateTemperature, однако кто вспомнит, что есть класс DateTime, который тоже нужно исправить и наоборот. Для решения этой проблемы мы должны были воспользоваться принципом SRP (Single responsibility principle) и изначально создать классы DateTime и Temperature. А если бы нам был нужен класс-фасад, то тогда бы мы могли создать DateTemperature, который бы содержал оба эти класса, а по средствам композиции делегировал бы им выполнение.
Композиция
Большая гибкость. Сокрытые свойства находятся под полным контролем.
Времени компиляции. Хранение объекта посредством указателя, а не в виде непосредственного члена или базового класса позволяет также снизить зависимости, поскольку объявление указателя на объект не требует полного определения класса этого объекта. Наследование, напротив, всегда требует видимости полного определения базового класса.
Непредсказуемое поведение. Достаточно забыть вызвать super и уже даже этот тонкий момент с трудом поддастся отладке, не говоря уже о глубокой цепочке наследования, где взаимосвязь вызова методов будет довольно сложно уследить.
Переиспользуемость. Не все классы проектируются с учетом того, что они будут выступать в роли базовых. Но большинство классов вполне могут быть членами.
Безопасность.
Хрупкость. Наследование приводит к дополнительным усложнениям, таким как сокрытие имен и другим, возникающим при внесении изменений в базовый класс.
Легкая тестируемость.
Это все слабые аргументы против наследования. Наследование предоставляет очень большие возможности, чего только стоит заменимость отношения на и/или, возможность перекрытия методов и т.д. Но я рекомендую всегда задумываться за что вы платите. Если вы можете обойтись без наследования, вам незачем мириться с его недостатками. Давайте выделим моменты, где наследование действительно необходимо:
Управляемый полиморфизм, заменимость.
Почему абстракции так важны
На мой взгляд протоколы в Swift наделены рядом довольно удобных фишек: extensions, optional методы и т.д., но все эти инструменты, нарушают понятия абстракции. В первую очередь, абстракция помогает нам сосредоточиться на проблемах правильного абстрагирования, не вдаваясь в детали реализации или тем более управления состояниями, что дает нам Swift через его extensions. В моем понимании протокол должен представляет собой подобие абстрактного класса, а не базового, составленного полностью из (чисто) виртуальных функций и не обладающий состояниями, членами-данными, не иметь реализаций функций-членов. Их реализация в протоколах усложняет дизайн всей иерархии.
Предпочитайте определять правильные абстрактные протоколы и выполнять наследование от них, следуя принципу DIP (Dependency inversion principle). Корнями всей иерархий должны быть абстрактные классы или протокол, в то время как конкретные классы в этой роли выступать не должны. Абстрактные базовые классы или протоколы должны беспокоиться об определении функциональности, но не о ее реализации. Правильная абстракция нам дает:
Надежность. Менее стабильные части системы (конкретные реализации) зависят от более стабильных частей (абстракций).
Гибкость. Если абстракции корректно смоделированы, то при появлении новых требований легко разработать новые реализации.
Модульность. Дизайн, опирающийся на абстракции, обладает хорошей модульностью благодаря простоте зависимостей.
Наследование не для повторного использования
ООП как парадигма известна программистам уже многие годы, однако часто можно заметить, что цель наследования понимается неверно, и во многих случаях применение наследования оказывается неверным. Применять наследование для того, чтобы повторно использовать код находящийся в базовом классе воистину плохая идея. В первую очередь, наследование необходимо для того, чтобы быть повторно использованным существующим кодом, который полиморфно использует объекты базового класса.
Используя наследование всегда стоит помнить о LSP (Liskov substitution principle), данным принцип говорит нам как правильно моделировать отношения между базовым классом и его наследниками, а именно: «является», «работает как», «используется как». Все условия базового класса должны быть выполнены, все перекрытые методы не должны требовать и обещать больше или меньше, чем их базовые версии.
Отношение «является» очень часто понимается неверно, общеизвестный пример с квадратом и прямоугольником, где квадрат «является» прямоугольником, но с точки зрения поведения квадрат не является прямоугольником. Вот почему вместо «является» стоит предпочитать говорить «работает как» для того, чтобы такое описание воспринималось максимально правильно.
Виртуальные функции
Как было описано в разделе о классификации классов, язык Swift обладает рядом ограничений для построения базовых классов, нам мешает отсутствие виртуальных методов и в некоторых случаях модификатор доступа protected. На данный момент обеспечить защищённый доступ к методу или свойству пока нет, но для построения открытой невиртуальной функции можно использовать ключевое слово final для предотвращения переопределения, тем самым можно будет четко разделить виртуальные функции, которые можно переопределить и просто открытые методы базового класса. Виртуальная функция решает две различные параллельные задачи:
Определение интерфейса. Будучи открытой, такая функция является непосредственной частью интерфейса класса, предоставленного внешнему миру.
Определение деталей реализации. Будучи виртуальной, функция предоставляет производному классу возможность заменить базовую реализацию этой функции (если таковая имеется), в чем и состоит цель настройки.
В связи с существенным различием целей этих двух задач, совершенно очевидно, что у функции недостаточно хорошее разделение зоны ответственности. Путем разделения открытых функций от виртуальных мы достигаем следующих значительных преимуществ:
Естественный вид. Когда мы разделяем открытый интерфейс от интерфейса настройки, каждый из них может легко приобрести тот вид, который для него наиболее естественен, не пытаясь найти компромисс, который заставит их выглядеть идентично. Зачастую эти два интерфейса требуют различного количества функций и/или различных параметров; например, внешняя вызывающая функция может выполнить вызов одной открытой функции Process, которая выполняет логическую единицу работы, в то время как разработчик данного класса может предпочесть перекрыть только некоторые части этой работы, что естественным образом моделируется путем независимо перекрываемых виртуальных функций (например, DoProcessPhase1, DoProcessPhase2), так что производному классу нет необходимости перекрывать их все.
Устойчивость. Мы можем позже добавить некоторую проверку условий, разделить выполнение работы на большее количество шагов или вовсе переделать ее, реализовать более полное разделение интерфейса или внести иные изменения в базовый класс, и все это никак не повлияет на код, использующий данный класс или наследующий его. Заметим, что ситуация окажется существенно сложнее, если начать с открытых виртуальных функций и позже изменять их, что неизбежно приведет к изменениям либо в коде, который использует данный класс, либо в наследующем его.
Свойства класса должны быть закрытыми
Наличие открытых свойств означает, что состояние вашего класса может изменяться непредсказуемо. Открытые свойства даже хуже простейших функций для получения и установки значений.
Использование функций для получения и установки значений равносильны тому, что закрыть свою квартиру на замок и оставить ключ в замке. Такие функции явно нарушают инкапсуляцию, путем доступа к данным из вне, однако это все же более безопасный вариант, поскольку он хотя бы может обеспечить устойчивость кода к возможным внесенным изменениям путем проверок. Также иногда классы обязаны предоставить доступ ко внутренним данным по причинам, связанным с совместимостью, например, для интерфейса со старым кодом или при использовании других систем.
Защищенные свойства обладают всеми недостатками открытых данных, в качестве примера, можно создать производный класс и использовать его для доступа к данным. Читать и модифицировать защищенные данные так же легко, как и открытые.
Вывод
В данной статье я описал ключевые вопросы с пояснением касательно проектирования классов на языке Swift. Мы рассмотрели как проектировать классы правильно, как не допускать ошибки, избегать проблем и как правильно управлять зависимостями между объектами.
Объектно-ориентированное программирование в 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(_ : ), в классе 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. И, для более подробного изучения языка, вы можете пройти наши курсы!










