Вычисляемые свойства и наблюдатели
Представьте бланк Rect с графами «ширина» и «высота». Ниже есть третья графа, «площадь», но вписывать в неё ничего не нужно: рядом напечатано правило «ширина × высота». Кто читает бланк, тот перемножает два числа сам и всегда получает свежий ответ.
Если бы площадь вписывали от руки, после исправления ширины она бы устарела. Правило вместо числа защищает от такой ошибки. В Swift графу с правилом записывают так:
struct Rect {
var width: Double
var height: Double
var area: Double {
width * height
}
}
var r = Rect(width: 2, height: 3)
print(r.area) // 6.0
r.width = 4
print(r.area) // 12.0Давайте разберём программу по шагам:
widthиheight— обычные свойства с данными.- У
areaпосле типа стоят фигурные скобки с выражением: это правило, а не данные. Rect(width: 2, height: 3)заполняет только ширину и высоту: значения дляareaв скобках нет.- Чтение
r.areaвыполняет правило, и программа печатает6.0. - После
r.width = 4то же чтениеr.areaдаёт уже12.0.
Хранимые и вычисляемые свойства
Свойства, которые держат значение в самом экземпляре, называют хранимыми. Такими были все свойства структур до этого урока. А area — вычисляемое свойство, как у перечислений: оно ничего не хранит и при каждом чтении заново вычисляет значение по своему правилу.
Как объявляют вычисляемое свойство:
- всегда через
var, даже если его только читают: сletкомпилятор ответит'let' declarations cannot be computed properties; - тип указывают обязательно:
var area: Double; - после типа идёт тело в фигурных скобках, как у функции; если в теле одно выражение, оно возвращается без
return.
Площадь можно было бы получать и методом func area() -> Double. Но вычисляемое свойство читается как данные, без скобок: r.area. Его выбирают, когда значение — это характеристика самого экземпляра, а не действие.
Наблюдатели свойства
Иногда при каждом изменении свойства нужно что-то сделать: записать изменение в журнал или сообщить о нём. Представьте штамп на графе бланка: «При исправлении напишите на полях, что было и что стало». Такой штамп в Swift называют наблюдателем свойства. Его пишут в фигурных скобках после объявления хранимого свойства.
didSet
didSet срабатывает сразу после того, как в свойство записали новое значение:
struct Thermostat {
var degrees = 20 {
didSet {
print("was \(oldValue), now \(degrees)")
}
}
}
var t = Thermostat(degrees: 18)
t.degrees += 2 // was 18, now 20
t.degrees = 25 // was 20, now 25
print(t.degrees) // 25Давайте разберём, когда наблюдатель срабатывает:
Thermostat(degrees: 18)создаёт экземпляр, и наблюдатель молчит: пока экземпляр создаётся, свойства заполняются без наблюдателей.t.degrees += 2записывает в свойство новое значение, иdidSetпечатаетwas 18, now 20. Запись через+=— тоже запись.- Внутри
didSetсвойствоdegreesуже хранит новое значение, а прежнее доступно под именемoldValue. - После
t.degrees = 25вoldValueлежит20— значение перед этой записью, а не начальное18.
willSet
willSet срабатывает перед записью. Свойство в этот момент ещё хранит старое значение, а новое доступно под именем newValue:
struct Battery {
var level = 100 {
willSet {
print("\(level) -> \(newValue)")
}
}
}
var phone = Battery()
phone.level = 80 // 100 -> 80phone.level = 80 печатает 100 -> 80: внутри willSet свойство level ещё равно 100. У одного свойства могут быть оба наблюдателя сразу.
Здесь картинка со штампом неточна. Человек может пропустить штамп или не записывать исправление, которое ничего не меняет. Swift вызывает наблюдатель при каждой записи, даже если новое значение равно старому.
Частая ошибка
В вычисляемое свойство попытались записать значение:
r.area = 10 // ✗error: cannot assign to property: 'area' is a get-only propertyПрограмма не компилируется. area — правило, а не графа с данными, и записать в него некуда. Словами get-only компилятор называет свойство, которое можно только читать. Чтобы площадь стала другой, меняйте хранимые свойства width и height.
Главное
- Хранимое свойство держит значение в экземпляре, а вычисляемое ничего не хранит и вычисляет значение при каждом чтении.
- Вычисляемое свойство объявляют через
varс типом и телом в фигурных скобках; записать в него значение нельзя. willSetсрабатывает перед записью и видитnewValue,didSet— после записи и видитoldValue.- Наблюдатели молчат, пока экземпляр создаётся, и срабатывают при каждой записи, в том числе через
+=.