Методы и mutating
Вернёмся к бланку Point. Внизу на нём напечатаны две инструкции: «как записать точку в скобках» и «как сдвинуть точку вправо на несколько шагов». Каждая инструкция работает с графами того бланка, на котором она напечатана.
Такие действия можно написать и отдельными функциями, которые получают точку параметром. Но инструкцию удобнее держать рядом с графами: сразу видно, что умеет бланк, и искать её по всей программе не нужно. В Swift для этого есть методы:
struct Point {
var x: Int
var y: Int
func describe() -> String {
"(\(x), \(y))"
}
mutating func moveRight(by step: Int) {
x += step
}
}
var p = Point(x: 1, y: 2)
print(p.describe()) // (1, 2)Давайте разберём программу по шагам:
- Внутри
struct Point, рядом со свойствами, объявлены два метода:describeиmoveRight. var p = Point(x: 1, y: 2)создаёт экземпляр.p.describe()вызывает метод через точку, у конкретного экземпляраp. Метод собирает строку из его свойств и возвращает(1, 2).
Слово mutating перед вторым методом разберём чуть ниже.
Метод структуры
Метод у структуры такой же, как у перечислений: это функция внутри фигурных скобок типа. Его вызывают через точку у экземпляра, p.describe(). Всё, что вы знаете о функциях, работает и здесь: параметры, метки аргументов, результат после ->.
Главное отличие от обычной функции: метод видит свойства своего экземпляра. Внутри describe стоят только имена x и y: это свойства того экземпляра, у которого вызвали метод. Для p это 1 и 2, а у другой точки будут её собственные числа.
Слово self
Внутри метода self — это экземпляр, у которого метод вызвали. Запись x в теле метода — сокращение от self.x.
Обычно self не пишут. Он нужен, когда у параметра то же имя, что у свойства:
- в методе с параметром
xзаписьxозначает параметр; self.xв том же методе означает свойство.
Метод, который меняет свойство
Здесь картинка с инструкциями неточна. На бумаге по любой инструкции можно исправить графу, а в Swift метод по умолчанию свойства только читает. Метод moveRight меняет свойство x, поэтому перед его func стоит mutating. Вызовем его:
p.moveRight(by: 9)
print(p.describe()) // (10, 2)После вызова в p.x лежит 10, а y не изменился.
Если убрать mutating, программа не скомпилируется:
error: left side of mutating operator isn't mutable: 'self' is immutableКомпилятор говорит: левую часть += менять нельзя, потому что self неизменяем. С обычным присваиванием x = x + step сообщение звучит иначе: cannot assign to property: 'self' is immutable. Суть в обоих одна — 'self' is immutable.
Почему так? Вспомните параметры функции: параметр — константа, и менять его в теле нельзя. Внутри метода self — такая же константа. Слово mutating разрешает методу менять self и работает как inout. Метод получает экземпляр, меняет его, а при выходе изменения записываются обратно в p.
Метод без mutating свойства читает, но не меняет, как describe. Поэтому mutating ставят только тем методам, которые меняют свойства.
Этот вопрос часто задают на собеседовании: зачем методу структуры mutating и почему его нельзя вызвать у let? Ответ состоит из двух частей:
- внутри метода
self— константа, поэтому метод, который меняет свойство, помечаютmutating, и он получаетselfкак сквозной параметр; - экземпляр в
letне может меняться вовсе, поэтому вызовmutating-метода у него не компилируется.
Частая ошибка
mutating-метод вызвали у экземпляра в let:
let home = Point(x: 0, y: 0)
home.moveRight(by: 1) // ✗error: cannot use mutating member on immutable value: 'home' is a 'let' constantПрограмма не компилируется, и не выполняется ни одна строка. Swift не делает втихую копию home, чтобы сдвинуть её: экземпляр в let — запечатанный бланк, а moveRight исправляет на нём графу. Как и параметру inout, mutating-методу нужна переменная. Объявите экземпляр через var, и вызов заработает.
Главное
- Метод — функция внутри типа; его вызывают через точку у экземпляра, и он видит свойства этого экземпляра.
self— экземпляр, у которого вызвали метод;self.xпишут, когда у параметра то же имяx.- Метод, который меняет свойство, помечают
mutating: без негоselfвнутри метода — константа. mutating-метод нельзя вызвать у экземпляра вlet: такая программа не компилируется.