Перейти к содержимому
Теория · Классы · урок 3 из 10

let и ссылка на объект

Вернёмся к листу и биркам на нитях. Если имя объявлено через var, его бирку можно отвязать и привязать к другому листу. Бирку имени let привязали намертво: она всегда ведёт к одному и тому же листу. Но писать на самом листе это не мешает.

SWIFT
class Document {
    var title = "Plan"
}

let doc = Document()
doc.title = "Final"
print(doc.title) // Final

Давайте разберём, почему это компилируется:

  1. let doc = Document() создаёт экземпляр и кладёт в константу doc ссылку на него.
  2. doc.title = "Final" меняет не ссылку, а свойство экземпляра. Ссылка в doc осталась прежней, поэтому let ничего не запрещает.
  3. print(doc.title) печатает Final.

А вот привязать бирку doc к другому листу не получится. Строка doc = Document() даёт ошибку:

TEXT
error: cannot assign to value: 'doc' is a 'let' constant

Будь doc переменной var, такое присваивание сработало бы, и doc повела бы к новому экземпляру.

let у структуры и у класса

Константа let запрещает менять то, что лежит в ней самой. Разница между структурой и классом — в том, что именно там лежит.

Структура

В константе структуры лежит сам бланк со всеми полями. let запечатывает его целиком: нельзя ни заменить бланк, ни исправить в нём поле.

SWIFT
struct Form {
    var title = "Plan"
}

let form = Form()
form.title = "Final" // ✗
TEXT
error: cannot assign to property: 'form' is a 'let' constant

Программа не компилируется: изменить свойство структуры — значит изменить значение, лежащее в константе form.

Класс

В константе класса лежит только ссылка — бирка. let закрепляет бирку, а экземпляр, к которому она ведёт, остаётся изменяемым. Даже если все имена экземпляра — константы, его меняют через любое из них:

SWIFT
let original = Document()
let draft = original
draft.title = "Draft"
print(original.title) // Draft

Разберём по строкам:

  1. original и draft — две константы с одной и той же ссылкой.
  2. draft.title = "Draft" меняет общий экземпляр.
  3. original.title теперь тоже Draft: let не защитил экземпляр от правки через другое имя.

То же происходит с параметром функции. Параметр — константа, но для класса это константа со ссылкой: функция может изменить свойства переданного ей экземпляра, и вызывающий код увидит правку.

Здесь картинка с общим документом неточна. Ссылку на настоящий онлайн-документ можно дать «только для просмотра», а let такой ссылки не делает: через константу экземпляр всё равно меняют. Защитить поле можно иначе — объявить его в самом классе через let. Значение такому свойству задают в объявлении или в init, и потом его не изменить ни через какую ссылку.

На собеседовании часто спрашивают, чем let у структуры отличается от let у ссылки на класс. Ответ:

  • у структуры let запрещает и заменить значение, и менять его свойства;
  • у класса let запрещает только перевести ссылку на другой экземпляр, а свойства var у экземпляра меняются.

Частая ошибка

Структуру переделали в класс и оставили у метода слово mutating:

SWIFT
class Counter {
    var count = 0
    mutating func increment() { // ✗
        count += 1
    }
}
TEXT
error: 'mutating' is not valid on instance methods in classes

Программа не компилируется. В структуре mutating разрешает методу менять свойства, потому что метод меняет само значение. Метод класса меняет экземпляр, а не ссылку, поэтому слово mutating ему не нужно, и компилятор его запрещает.

Уберите mutating. Метод класса, меняющий свойства, можно вызвать даже у константы let: let clicks = Counter() и затем clicks.increment() компилируются.

Главное

  • let у ссылки на класс запрещает только привязать её к другому экземпляру: doc = Document() не скомпилируется.
  • Свойства var экземпляра класса меняются через любую ссылку, даже через константу let.
  • let у структуры замораживает всё значение: нельзя ни заменить его, ни изменить его свойства.
  • Методы класса меняют свойства без mutating, а неизменяемое поле объявляют через let внутри класса.