let и ссылка на объект
Вернёмся к листу и биркам на нитях. Если имя объявлено через var, его бирку можно отвязать и привязать к другому листу. Бирку имени let привязали намертво: она всегда ведёт к одному и тому же листу. Но писать на самом листе это не мешает.
class Document {
var title = "Plan"
}
let doc = Document()
doc.title = "Final"
print(doc.title) // FinalДавайте разберём, почему это компилируется:
let doc = Document()создаёт экземпляр и кладёт в константуdocссылку на него.doc.title = "Final"меняет не ссылку, а свойство экземпляра. Ссылка вdocосталась прежней, поэтомуletничего не запрещает.print(doc.title)печатаетFinal.
А вот привязать бирку doc к другому листу не получится. Строка doc = Document() даёт ошибку:
error: cannot assign to value: 'doc' is a 'let' constantБудь doc переменной var, такое присваивание сработало бы, и doc повела бы к новому экземпляру.
let у структуры и у класса
Константа let запрещает менять то, что лежит в ней самой. Разница между структурой и классом — в том, что именно там лежит.
Структура
В константе структуры лежит сам бланк со всеми полями. let запечатывает его целиком: нельзя ни заменить бланк, ни исправить в нём поле.
struct Form {
var title = "Plan"
}
let form = Form()
form.title = "Final" // ✗error: cannot assign to property: 'form' is a 'let' constantПрограмма не компилируется: изменить свойство структуры — значит изменить значение, лежащее в константе form.
Класс
В константе класса лежит только ссылка — бирка. let закрепляет бирку, а экземпляр, к которому она ведёт, остаётся изменяемым. Даже если все имена экземпляра — константы, его меняют через любое из них:
let original = Document()
let draft = original
draft.title = "Draft"
print(original.title) // DraftРазберём по строкам:
originalиdraft— две константы с одной и той же ссылкой.draft.title = "Draft"меняет общий экземпляр.original.titleтеперь тожеDraft:letне защитил экземпляр от правки через другое имя.
То же происходит с параметром функции. Параметр — константа, но для класса это константа со ссылкой: функция может изменить свойства переданного ей экземпляра, и вызывающий код увидит правку.
Здесь картинка с общим документом неточна. Ссылку на настоящий онлайн-документ можно дать «только для просмотра», а let такой ссылки не делает: через константу экземпляр всё равно меняют. Защитить поле можно иначе — объявить его в самом классе через let. Значение такому свойству задают в объявлении или в init, и потом его не изменить ни через какую ссылку.
На собеседовании часто спрашивают, чем let у структуры отличается от let у ссылки на класс. Ответ:
- у структуры
letзапрещает и заменить значение, и менять его свойства; - у класса
letзапрещает только перевести ссылку на другой экземпляр, а свойстваvarу экземпляра меняются.
Частая ошибка
Структуру переделали в класс и оставили у метода слово mutating:
class Counter {
var count = 0
mutating func increment() { // ✗
count += 1
}
}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внутри класса.