Туториал: Объектно-ориентированное программирование в Swift

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

Xcode: 
9
Swift: 
4.0

Объектно-ориентированное программирование - это фундаментальная парадигма программирования, которую обязательно нужно освоить, для использования и знания языка Swift на более высоком и професcиональном уровне. Объектно-ориентированное программирование лежит в основе большинства фреймворков, с которыми в будущем вы будете работать. Разбираясь в проблеме взаимодействия между объектами с помощью сообщений, которыми объекты обмениваются, может привести сперва к непониманию, но это проверенный способ построения сложных систем, который берет свое начало еще с 1950-х

Почти в любой модели мы можем использовать объекты, для примера возьмем – координаты на карте, касания экрана, даже изменения процентных ставок на банковском счете. На старте изучения ООП, было бы здорово, если вы попробовали бы представить моделирование физических вещей в реальном мире, прежде чем применить их это на более абстрактных понятиях. Ведь почти все можно представить в виде объектов.

В этом уроке мы будем использовать объектно-ориентированное программирование для того, чтобы создать свою собственную модель объектов, для нашего примера возьмем группу музыкальных инструментов. Также в этой статье вы также узнаете очень много важных концепций, включая:

- Инкапсуляция

- Наследование

- Переопределение и перегрузка методов

- Типы и экземпляры

- Композиции

- Полиморфизм

- Модификаторы доступа

По содержанию видно, что придется изучить многое, но уверяю, что будет интересно, так что запаситесь временем, кружкой кофе и давайте начнем! :]

Поехали!

Запустите Xcode и перейдите в: File \ New \ Playground. Введите название для проекта: «Instruments», затем выберите платформу «iOS» и нажмите «Далее». Выберите место для хранения вашего проекта и нажмите кнопку «Create». Когда откроется Playground, с вашим новым проектом, очистите его полностью, чтобы лишняя информация вам не мешала

Проектирование объектов объектно-ориентированным способом обычно начинается с самой концепции построения, затем распространяется уже на конкретные типы. Вы хотите создавать музыкальные инструменты, поэтому имеет смысл начинать с типа Instrument (тип Instrument будет общий для всех), а затем определять конкретные (не буквально!) инструменты, такие как пианино и гитары. Подумайте об этом как о генеалогическом дереве инструментов, где все течет от общего типа к конкретному сверху вниз. Пример:

Заметка

Для начала давайте обсудим следующее определения, они будут встречаться в статье, поэтому всегда можно будет вернуться в этот раздел, если вдруг вы забудете значение:
is-a - public наследование.
has-a - агрегирование (создается в классе).
use-a - агрегирование (приходят извне, доступны не только в классе).

Связь между дочерним типом и его родительским типом является отношением is-a. То есть отношением объектов друг к другу, через наследование. Например, мы видим, что «Guitar is-a Instrument». То есть «Instrument» – это родитель, а «Guitar» по отношению к типу «Instrument» является дочерним типом – Гитара является наследником типа Инструмент. Теперь, когда у вас есть визуальное представление объектов, с которыми мы будем работать, можно начать реализацию нашего проекта.

Свойства

Добавьте в Playground  следующий блок кода:

// 1 
class Instrument {   
	// 2   
	let brand: String   
	// 3  
	init(brand: String) {
		//4      
		self.brand = brand   
	}
}

Теперь давайте разберем, что мы написали выше:

  1. Вы создаете базовый класс Instrument с ключевым словом class. Это будет корневой класс по иерархии всех будущих инструментов. Он определяет «Чертеж/План», который будет содержаться в основе любого созданного инструмента (дочерних классов). Поскольку это тип, имя должно начинаться с заглавной буквы (стоит запомнить!), в нашем примере с большой буквы начинается тип Instrument, это является обязательным соглашение в Swift.
  2. Затем объявили свойства (данные) инструмента, которые будут у всех будущих инструментов. В нашем случае это свойство brand, которому мы присвоили тип строки (String).
  3. Теперь нужно создать инициализатор для класса, он обозначается ключевым словом init. Его цель - это создание новых инструментов путем инициализации всех свойств (stored properties).
  4. На этом шаге мы как раз устанавливаем данные в свойство brand из того, что будет приходить в параметрах метода. Поскольку свойство и параметр имеют одинаковое имя, нужно использовать ключевое слово self, чтобы можно было инициализировать именно нашу локальную переменную.

Сейчас вы только что реализовали класс для будущих инструментов, который содержит одно свойство brand, но еще не задали для него поведение. Поведение – означает, что мы можем повлиять на состояние свойства (объекта), через реализацию метода(ов). Давайте добавим какое-нибудь поведение в виде методов для наших свойств.

Методы

Как уже и говорилось, с помощью методов вы можете задать поведение свойствам, например сделать метод настройки (tune() ) или попросить сыграть на инструменте (play() ), при этом независимо от конкретного типа свойств. Добавьте следующий код внутри класса Instrument сразу после инициализатора:

func tune() -> String {   
	fatalError("Implement this method for \(brand)")
 }

Метод tune() - это функция, которая выполниться, только при условии, что мы ее вызовем в наследуемом классе. Классы с подобными методами называются абстрактными, потому что они не предназначены для обычного использования. Чтобы использовать их, вы должны определить подкласс, который переопределит этот метод tune(), после переопределения можно реализовать логику данного метода, а не просто вызвать fatalError(). Подробнее об этом мы поговорим позже.

Функции, определенные внутри класса, называются методами, поскольку они имеют доступ к свойствам, в нашем случае таким свойством является brand, свойство класса Instrument. Организация свойств и связанных с ними операций в классе - это мощный инструмент для упрощения сложных процессов. 

Такого рода реализация носит название «Инкапсуляция». Тип класса описывает инкапсулированные данные (например, свойства хранения) и их поведение (например, методы).

Теперь давайте добавим следующий код, но только уже перед нашим классом Instrument:

class Music  {   
	let notes: [String]   
 
	init(notes: [String])  {     
		self.notes = notes   
	}    

	func prepared() -> String  {     
		return notes.joined(separator: " ")   
	} 
}

Это класс Music, который инкапсулирует массив нашего свойства notes с типом String и позволяет задать поведение, в нашем примере мы добавили поведение, которое может соединить все приходящие данные в свойстве, в одну строку, это метод prepared().

Теперь добавьте следующий метод в класс Instrument сразу после метода tune():

func play(_ music: Music) -> String {   
	return music.prepared() 
}

Метод play(_ :) возвращает строку (String), музыку которую нужно воспроизвести. Возможно у вас возник вопрос, зачем нам создавать специальный тип Music, а не просто передать весь массив строк (String). Дело в том, что это дает ряд преимуществ: создание типа Music помогает создавать словарь, что позволит компилятору проверить корректность работы и создаст место для будущих расширений.

Теперь добавьте следующий метод в класс Instrument сразу после метода play(_ :):

func perform(_ music: Music) {   
	print(tune())   
	print(play(music)) 
}

Метод perform(_ :) задает поведение настройки инструмента, а затем воспроизводит музыку. Мы создали два метода рядом, чтобы мы могли насладиться настроенной, красиво звучащей симфонией. :]

Это все касалось реализации класса Instrument. Сейчас самое время добавить уже конкретные инструменты.

Наследование

Добавьте следующий класс в нижней части Playground, сразу после класса Instrument:

// 1 
class Piano:  Instrument  {
	let hasPedals: Bool   
	// 2   
	static let whiteKeys = 52   
	static let blackKeys = 36      
	// 3   
	init(brand: String, hasPedals: Bool = false) {             
	       self.hasPedals = hasPedals     
	   // 4     
	   super.init(brand: brand)   
	}      
	// 5   
	override func tune() -> String {     
		return "Piano standard tuning for \(brand)."   
	}      
	override func play(_ music: Music) -> String {     
	// 6      
	let preparedNotes = super.play(music)     
	     return "Piano playing \(preparedNotes)"   
	} 
}

Давайте разберем, шаг за шагом:

  1. Вы создаете класс Piano в качестве подкласса родительского класса Instrument. Все свойства и методы автоматически наследуются дочерним классом Piano и доступны для пользования.
  2. Любое пианино, которое захотите создать, будет иметь одинаковое количество белых и черных клавиш, независимо от их бренда (brand). Динамически эти свойства клавиш с их значениями никогда не изменятся, потому что мы указали свойство как static.
  3. Для параметра hasPedals, инициализатор предоставляет значение по умолчанию false, которое при желание можно будет изменить.
  4. В этом месте мы используем ключевое слово super, чтобы вызвать инициализатор родительского класса, сразу после установки свойства hasPedals  дочернего класса. Инициализатор суперкласса заботится об инициализации унаследованных свойств - в данном случае это свойство brand.
  5. Переопределяем реализацию унаследованного метода tune() с ключевым словом override. Это обеспечивает реализацию метода tune(), которая не вызовет fatalError(), а сделает что-нибудь в классе Piano, например распечатает брэнд настроенного пианино.
  6. Здесь происходит переопределение унаследованного  метода  play(_ :). С этим методом вы используете ключевое слово super, это делается для того, чтобы вызвать родительский метод Instrument, затем получить подготовленные музыкальные ноты и сразу начать игру на пианино.

Поскольку класс Piano наследуется от класса Instrument, он уже совершенно точно знает о классе Instrument, что: у него есть свойство brand, есть метод для настройки музыки tune(), есть метод проигрывания музыки play() и метод выполняющий воспроизведение музыки с нашими настройками perform().

Заметка

В классах Swift используется процесс инициализации, называемый двухфазной инициализацией, чтобы дать гарантию, что все свойства будут инициализированы до их использования. Если вы хотите узнать больше об инициализации, ознакомьтесь с нашей учебной серией по инициализации Swift.

Конечно вся музыка при игре на пианино издает какие либо звуки, но ведь звучать оно может по-разному. Поэтому пришло время добавить в вашу музыку педали, чтобы звучание можно было менять по нашему вкусу. :]

Перегрузка методов

Добавьте следующий метод в класс Piano, сразу после переопределенного метода play(_ :):

func play(_ music: Music, usingPedals: Bool) -> String {   
	let preparedNotes = super.play(music)   
	if hasPedals && usingPedals {     
	    return "Play piano notes \(preparedNotes) with pedals."       
	} else { 
	    return "Play piano notes \(preparedNotes) without pedals."   
	} 
}

Добавление этого метода перегружает метод play(_ :) для задействования педалей, при условии, что usePedals у нас true, и у пианино доступны педали в параметрах. Мы не задействовали ключевое слово override, потому что у нового созданного метода play() отличается список параметров, в отличии от переопределенного метода play(). Swift использует список параметров (сигнатуру), чтобы автоматически определить, требуется ли переиспользование метода. Следует быть осторожными с перегрузкой методов, потому что они могут вас запутать. Например, метод perform(_ :) всегда вызывает play (_ :) и только его, он никогда не вызовет наш второй созданный метод play(_: usingPedals :).

Сделайте в методе play(_ :), в классе Piano, другую реализацию метода, которая будет вызывать другую педаль, добавьте код ниже:

override func play(_ music: Music) -> String {   
	return play(music, usingPedals: hasPedals) 
}

Теперь пришло время попробовать с созданием экземпляров класса Piano, мы сможем сделать для него необходимые настройки и воспроизвести на нем действительно классную музыку. :]

Экземпляры

Добавьте следующий блок кода в конце Playground  сразу после объявления класса Piano:

// 1 
let piano = Piano(brand: "Yamaha", hasPedals: true) piano.tune() 
// 2 
let music = Music(notes: ["C", "G", "F"]) piano.play(music, usingPedals: false) 
// 3 
piano.play(music) 
// 4 
Piano.whiteKeys 
Piano.blackKeys

Теперь разберемся, что у нас тут происходит:

  1. Объявили переменную piano, как экземпляр класса Piano и инициализировали все необходимые данные. Обратите внимание, что в то время как типы (классы) начинаются с заглавной буквы, экземпляры всегда начинаются с маленькой. Опять же, это является обязательным соглашением Swift.
  2. Тут вы объявляете переменную music, как экземпляр класса Music, заполняете необходимые данные и далее задаете поведение игры на пианино по нотам, с помощью нашего перегруженного метода play(), которая позволит воспроизвести песню уже без использования педалей, так как мы передали в параметр false.
  3. Вызываем из класса Piano, версию метода play(_ :), которая всегда использует педали, при условии, что этот параметр доступен.
  4. Здесь указаны статические переменные (whiteKeys и blackKeys), которые хранятся внутри класса Piano, для их вызова нам не нужно создавать конкретный экземпляр класса – достаточно будет указать название класса и через точку указать свойство. Такие свойства называются – свойства типа, про это обязательно почитайте в официальной документации.

Теперь, когда у вас получилось наиграть мелодию на пианино, пришло время добавить гитарное соло в вашу музыку.

Промежуточный абстрактный базовый класс

Добавьте класс Guitar в самом конце вашего Playground:

class Guitar: Instrument  {   
	let stringGauge: String  
    
	init(brand: String, stringGauge: String = "medium") {    
		self.stringGauge = stringGauge     
		super.init(brand: brand)   
	} 
}

Мы создали новый класс Guitar, который вносит идею струнного датчика в виде текстового параметра строки (String) для базового класса Instrument. Подобно классу Instrument, подкласс Guitar считается абстрактным типом, методы tune () и play (_ :) должны быть переопределены в подклассе Guitar. Поэтому его иногда называют промежуточным абстрактным базовым классом.

Заметка

Вы заметите, что нет ничего, что помешало бы вам создать экземпляр абстрактного класса. Это так, и это ограничение для Swift. Некоторые языки позволяют вам конкретно указать, что класс является абстрактным и что вы не можете создать его экземпляр.

Теперь для классов, которые будут наследовать тип Guitar, вы сможете добавить действительно крутые гитарные партии! Давай сделаем это! :]

Конкретные гитары

Первый тип гитары, которую мы создадим, - давайте это будет акустическая. Добавьте класс AcousticGuitar в конец Playground сразу после вашего класса Guitar:

class AcousticGuitar: Guitar  {   
	static let numberOfStrings = 6   
	static let fretCount = 20      

	override func tune() -> String {     
		return "Tune \(brand) acoustic with E A D G B E"   
	}      

	override func play(_ music: Music) -> String {     
		let preparedNotes = super.play(music)     
		return "Play folk tune on frets \(preparedNotes)."   
	}
 }

Все акустические гитары имеют 6 струн и 20 ладов, нам потребуется  определить необходимые для этого свойства и определим их как static, поскольку они будут относиться ко всем акустическим гитарам. Static означает, что они являются константами, их значение никогда не изменится, ранее об этом мы уже говорили. В классе отсутствуют свойства, которым потребуется инициализация, поэтому создавать инициализатор нам не потребуется. Класс AcousticGuitar по умолчанию наследует инициализатор от своего родительского класса Guitar

Итак, время испытать нашу гитару!

Задачка

Нужно определить акустическую гитару Roland. Настроить ее и сыграть на ней.
Решение ниже!

Добавьте следующий код в нижней части Playground сразу после объявления класса AcousticGuitar:

let acousticGuitar = AcousticGuitar(brand: "Roland", stringGauge: "light") 
acousticGuitar.tune() 
acousticGuitar.play(music)

Пришло время немного пошуметь и сыграть какую-нибудь громкую музыку. Теперь уже нам потребуется усилитель, так что создадим его! :]

Уровень доступа Private

Акустические гитары играют превосходно, но с усилителем они будут играть гораздо лучше.  Добавьте класс Amplifier в нижней части Playground, чтобы начать нашу вечеринку:

// 1 
class Amplifier  {   
	// 2   
	private var _volume: Int   
	// 3   
	private(set) var isOn: Bool    

	init() {     
		isOn = false     
		_volume = 0   
	}    
	// 4   
	func plugIn() {     
		isOn = true   
	}    

	func unplug() {     
		isOn = false   
	}    
	// 5   
	var volume: Int {     
		// 6     
		get {       
			return isOn ? _volume : 0     
		}     
		// 7     
		set {      
			_volume = min(max(newValue, 0), 10)     
		}   
	} 
}

Здесь довольно много что у нас происходит, поэтому давайте разберёмся:

  1. Вы определяете класс Amplifier. Это такой же базовый класс, как и Instrument.
  2. Свойство _volume отмечено доступом private, это говорит о том, что что его можно получить только внутри класса Amplifier, а также будет скрыто от внешних пользователей. Нижнее подчеркивание в начале имени говорит о том, что свойство имеет скрытую реализацию данных. Еще раз, это все является неким соглашением в Swift, поэтому было бы хорошо, если вы всегда будете придерживаться этим правилам. :]
  3. Свойство isOn может быть доступно внешним пользователям, но только лишь для чтения значения. Данная конструкция разрешает получить значение поля объекта, но без установки в него значения, делается все это с помощью модификатора доступа private(set).
  4. PlugIn() и unplug() влияют на поведение isOn.
  5. Свойство с именем volume делает обертку над нашим приватным свойством _volume, чтобы мы могли задать необходимые для него правила.
  6. Геттер (get{}) понижает громкость до 0, если звук (volume) не подключен.
  7. Внутри сеттера (set{}) звук всегда будет держать ограничение по значениям от 0 до 10. Настройки на усиление громкости до 11 отсутствует.

Ключевым моментом является установка доступа private – это очень правильно для того, чтобы скрыть логику внутреннего кода и защитить ваш класс от нежелательных изменений. Для это есть свое название - «protecting the invariant».

Композиции

Теперь, когда у вас есть удобный компонент усилителя, пришло время использовать его в электрогитаре. Добавьте класс ElectricGuitar в конце Playground сразу после класса Amplifier:

// 1 
class ElectricGuitar: Guitar  {   
	// 2    
	let amplifier: Amplifier      
	// 3   
	init(brand: String, stringGauge: String = "light", amplifier: Amplifier) {     
		self.amplifier = amplifier     
		super.init(brand: brand, stringGauge: stringGauge)   
	}      
	// 4   
	override func tune() -> String {     
		amplifier.plugIn()     
		amplifier.volume = 5     
		return "Tune \(brand) electric with E A D G B E"   
	}      
	// 5   
	override func play(_ music: Music) -> String {     
		let preparedNotes = super.play(music)     
		return "Play solo \(preparedNotes) at volume \(amplifier.volume)."   	
	} 
}

Теперь пояснения:

  1. ElectricGuitar - это тип, который происходит от абстрактного, промежуточного базового класса Guitar.
  2. Электрическая гитара содержит свойство усилитель (amplifier). Это отношение has-a, а не отношения is-a, как в случае с наследованием.
    P.S. Если забыли, что такое has-a  и is-a  вернитесь в начало статьи :]
  3. Пользовательский инициализатор, который инициализирует все свойства хранения, а затем вызывает инициализатр суперкласса.
  4. Метод tune() с определенным поведением.
  5. Метод play() тоже с определенным своим поведением.

Аналогичным образом добавьте класс BassGuitar в нижней части Playground сразу после класса ElectricGuitar:

class BassGuitar: Guitar  {   
	let amplifier: Amplifier 
   
	init(brand: String, stringGauge: String = "heavy", amplifier: Amplifier) {     
		self.amplifier = amplifier     
		super.init(brand: brand, stringGauge: stringGauge)   
	}    

	override func tune() -> String {     
		amplifier.plugIn()     
		return "Tune \(brand) bass with E A D G"   
	}    

	override func play(_ music: Music) -> String {     
			let preparedNotes = super.play(music)     
			return "Play bass line \(preparedNotes) at volume \(amplifier.volume)."   
	} 
}

Мы только что создали бас-гитару, которая также использует has-a  по отношению к усилителю. Все этот класс завершили. Переходим к задаче ниже!

Задачка

Возможно, вы слышали, что классы следуют ссылочной семантике (reference semantics). Это означает, что переменные, содержащие экземпляр класса, фактически содержат ссылку на этот экземпляр. Если у вас есть две переменные с одной и той же ссылкой, изменение данных в одном изменит данные в другом, и на самом деле это одно и то же. Давайте посмотрим на ссылочную семантику в действии, создав экземпляр усилителя и разделив его между электрогитарой “Gibson” и бас-гитарой “Fender”.

Добавьте следующий код в нижней части Playground сразу после объявления класса BassGuitar:

let amplifier = Amplifier() 
let electricGuitar = ElectricGuitar(brand: "Gibson", 
					stringGauge: "medium", 
					    amplifier: amplifier) 
electricGuitar.tune()  

let bassGuitar = BassGuitar(brand: "Fender",  
			    stringGauge: "heavy", 
				 amplifier: amplifier) bassGuitar.tune()  

// Обратите внимание, что из-за семантики ссылки на класс усилитель является общим ресурсом между этими двумя гитарами.

bassGuitar.amplifier.volume
electricGuitar.amplifier.volume
bassGuitar.amplifier.unplug()
bassGuitar.amplifier.volume
electricGuitar.amplifier.volume
bassGuitar.amplifier.plugIn()
bassGuitar.amplifier.volume
electricGuitar.amplifier.volume

Полиморфизм

Одной из сильных сторон объектно-ориентированного программирования является возможность использовать разные объекты через один и тот же интерфейс, в то время когда каждый реализовывает свою логику. Это полиморфизм, означающий «many forms» (много-форменный). Добавьте класс Band в Playground:

class Band  {   
	let instruments: [Instrument]      

	init(instruments: [Instrument]) {     
		self.instruments = instruments   
	}      

	func perform(_ music: Music) {     
		for instrument in instruments {       
		instrument.perform(music)     
		}   
	} 
}

Класс Band имеет свойство массива instruments, которую мы определили в инициализаторе. Группа (band) выступает вживую на сцене, проходя через массив (array) инструментов (instruments) в цикле for in и вызывает метод perform(_ :) для каждого инструмента в массиве.

Теперь давайте продолжим и подготовим первый свой рок-концерт. Добавьте следующий блок кода в нижней Playground, сразу после класса Band:

let instruments = [piano, acousticGuitar, 
				electricGuitar, bassGuitar] 

let band = Band(instruments: instruments) band.perform(music)

Сначала вы определяете массив instruments (инструментов) из экземпляров класса Instrument, которые ранее мы создали. Затем вы объявляете объект band и настраиваете свойство своих instruments  (инструментов) с помощью инициализации в классе Band. Теперь, вы используете метод perform(_ :), экземпляра band,

чтобы задавать поведение группе и исполнять живую музыку (в Playground вы должны увидеть результат вашей музыки).

Обратите внимание, что, хотя тип массива instruments - [Instrument], каждый инструмент выполняет свое поеведение, в зависимости от его типа класса. Вот как полиморфизм работает на практике. Теперь в живую вы выступаете на концертах, как профессионал, поздравляю! :]

Модификаторы доступа

Вы уже видели 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 и удалите внутри все лишнее. И вставьте указанный код ниже:

// 1 
final public class Music  {   
	// 2   
	public let notes: [String]    
	public init(notes: [String]) {     
		self.notes = notes   
	}    
	public func prepared() -> String {     
		return notes.joined(separator: " ")   
	} 
}  
// 3 
open class Instrument  {   
	public let brand: String    
	public init(brand: String) {     
		self.brand = brand   
}    
// 4    
open func tune() -> String {     
	fatalError("Implement this method for \(brand)")   
}    

open func play(_ music: Music) -> String {     
	return music.prepared()   
}    
// 5
final public func perform(_ music: Music) {     
	print(tune())     
	print(play(music))   
	} 
}

Сохраните файл и вернитесь на главную страницу Playground. Не беспокойтесь, ваш проект будет работать как и прежде. 

Итак, что мы здесь сделали:

  1. final public означает, что будет доступен везде, но только для чтения, вы не сможете его переопределить или внести изменения.
  2. Каждое свойство, инициализатор и метод должны быть с модификатором доступа public, чтобы мы могли увидеть его из любого другого места.
  3. Класс Instrument отмечен как open, поскольку мы хотим дать разрешение на изменения.
  4. Методы также могут быть определены как open, это дает разрешение на их переопределение.
  5. Или методы могут быть отмечены как final, если мы хотим запретить их переопределить. Данный доступ дает полную гарантию за сохранность.

А что дальше?

Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!

Урок подготовил: Просвиряков Вадим

Источник урока.