Классы
Чему вы научитесь:
- объявлять класс с
initи объяснять, почему два имени могут вести к одному экземпляру; - понимать, что
letу класса закрепляет только ссылку, а не сам экземпляр; - наследовать класс, переопределять методы и проверять тип через
isиas?; - выбирать между структурой и классом и объяснять выбор на собеседовании.

Представьте лист бумаги с заголовком — документ. К листу на длинных нитях привязаны две бирки: на одной написано a, на другой — b. Потяните за любую бирку, и вы придёте к одному и тому же листу.
Перепишите строчку, придя по бирке b, и по бирке a вы увидите уже новую строчку. Лист один, к нему ведут две нити.
Так устроены общие онлайн-документы: ссылку на документ можно раздать многим людям, документ при этом остаётся один, и правку любого из них видят все.
В теме о структурах var b = a делало ксерокопию бланка, и копию заполняли отдельно. Класс устроен как общий документ. Посмотрим, как это выглядит в коде:
class Document {
var title = "Plan"
}
var a = Document()
var b = a
b.title = "Final"
print(a.title) // FinalДавайте разберём программу по строкам:
class Documentобъявляет класс с одним свойствомtitle. Как у структуры, у свойства есть значение по умолчанию.var a = Document()создаёт экземпляр класса — тот самый лист — и даёт имениaбирку к нему.var b = aне создаёт второй лист. Имяbполучает вторую бирку к тому же экземпляру.b.title = "Final"переписывает заголовок общего листа.print(a.title)печатаетFinal: биркаaведёт к тому же листу, и правка видна.
Будь Document структурой, программа напечатала бы Plan: b была бы отдельной копией.
Зачем нужен такой тип? Бывает, что несколько частей программы работают с одной и той же вещью: с банковским счётом, с персонажем игры, с открытым документом. Если каждая часть получит свою копию, изменения разойдутся. Класс даёт всем частям программы один общий экземпляр.
Ссылка вместо копии
Класс — тип, который, как и структура, объединяет свойства и методы, но при присваивании не копируется. В переменной класса лежит не сам экземпляр, а ссылка на него — та самая бирка на нити. Присваивание копирует ссылку, а экземпляр остаётся один. Экземпляр класса ещё называют объектом.
Класс — ссылочный тип, как и замыкание. А структуры, массивы, Int и String — значимые типы: у них присваивание копирует само значение.
Проверить, ведут ли две ссылки к одному экземпляру, можно оператором ===. Он отвечает true, только когда экземпляр один и тот же, а оператор !== отвечает наоборот. Работают они только с классами: у структуры нет ссылки, которую можно сравнить.
Нажимайте «Следующий шаг» и следите за a.title и b.title: они меняются вместе, потому что экземпляр один. А c появится сам по себе.
1class Document {2 var title = "Plan"3}4 5var a = Document()6var b = a7b.title = "Final"8print(a.title)9print(a === b)10let c = Document()11print(c.title)12print(a === c)
Document() создаёт экземпляр, а в a попадает ссылка на него.
Каждый вызов Document() создаёт новый экземпляр — новый лист. Поэтому c никак не связан с a и b, и a === c отвечает false.
Здесь картинка с листом неточна. Настоящий документ можно скопировать кнопкой, а у класса такой кнопки нет: var b = a всегда даёт ещё одну бирку, а новый лист создаёт только вызов Document().
На собеседовании джуниору часто дают программу со структурой и спрашивают, что она напечатает, если заменить struct на class. Ответ: структура при присваивании копируется, а экземпляр класса остаётся общим, и правка через одно имя видна через другое.
Класс и init
Структура сама получает почленный инициализатор, например Point(x: 1, y: 2). Класс почленного инициализатора не получает. Если у каждого свойства есть значение по умолчанию, у класса есть пустой init(): так мы и создавали Document().
Если хотя бы у одного свойства нет значения по умолчанию, init пишут сами:
class Account {
var owner: String
var balance = 0
init(owner: String) {
self.owner = owner
}
}
let anna = Account(owner: "Anna")
print(anna.owner, anna.balance) // Anna 0Разберём, как создаётся счёт:
- У
ownerнет значения по умолчанию, поэтому его должен задатьinit. init(owner: String)получает владельца параметром, иself.owner = ownerкладёт его в свойство нового экземпляра.balanceуже равен0, иinitего не трогает.Account(owner: "Anna")вызывает этотinit, аprintпечатаетAnna 0.
Частая ошибка
Класс объявили как структуру: со свойством без значения по умолчанию и без init.
class Profile {
var name: String // ✗
}error: class 'Profile' has no initializersПрограмма не компилируется: почленного инициализатора у класса нет, и задать name нечем. Под ошибкой компилятор называет и причину — свойство name без начального значения. Исправить это можно двумя способами:
- дать свойству значение по умолчанию:
var name = ""; - написать
init(name: String), как уAccount.
Главное
- Класс объявляют словом
class; в переменной класса лежит ссылка на экземпляр, а не сам экземпляр. var b = aкопирует ссылку:aиbведут к одному экземпляру, и правка через одно имя видна через другое.===проверяет, один ли это экземпляр; каждый вызов вродеDocument()создаёт новый.- Класс не получает почленный инициализатор: если у свойства нет значения по умолчанию, классу нужен свой
init.