Урок 19. Вычисляемые свойства
Главная / Уроки / Страница блока Основы / Урок 19. Вычисляемые свойства
23 Апреля 2023
Вычисляемые свойства
Вычисляемые свойства в Swift – это свойства, значения которых вычисляются каждый раз при доступе к ним, вместо того, чтобы хранить фиксированное значение, как это делают обычные свойства.
Звучит не просто, но давайте посмотрим на пример и разберем как это работает:
let radius = 6.0
var diameter: Double {
radius * 2
}
Свойство radius является обычной константой, тогда как diameter это вычисляемое свойство, разница между ними в том, что обычное свойство уже хранится в памяти, а вычисляемое сохранится только в тот момент когда мы к нему обратимся.
Обратите внимание на синтаксис вычисляемого свойства, тип данных должен быть прописан явно. Так же прошу заметить что таки свойства всегда являются переменными.
let radius = 6.0
var diameter: Double {
radius * 2
}
print(radius)
print(diameter)
В тот момент, когда срабатывает последний принт, будет произведен расчет и мы увидим результат на консоли:
Что бы понять как работает вычисляемое свойство, можно сравнить его с функцией, которая возвращает результат своей работы:
var diameter: Double {
radius * 2
}
func getDiameter() -> Double {
radius * 2
}
И в первом и во втором случае очень важно что бы внутри фигурных скобок в результате вернулся именно тот тип данных который был заявлен, в нашем случае это тип Double, и мы не можем вернуть что то другое. Оба варианта работают одинаково.
Теперь посмотрите на следующее вычисляемое свойство:
var greeting: String {
print("Hello, World!")
}
greeting
И ответьте на вопрос, чему будет равно свойство greeting при обращении к нему?
Если вы ответили “Hello, World!” то вам следует перечитать урок про функции с возвращаемым значением.
Вычисляемое свойство должно возвращать тип String, а что нам возвращает метод print? Правильно пустые скобки (), потому что этот метод ничего не возвращает. Компилятор нам так и скажет:
Мы должны вернуть именно строку:
var greeting: String {
"Hello, World!"
}
greeting // Hello, World!
Вот такой код будет отрабатывать отлично, и если мы обращаемся к свойству greeting мы получим нужную нам строку.
Давайте представим аналогию с функцией, для лучшего понимания:
var greeting: String {
"Hello, World!"
}
func getGreeting() -> String {
"Hello, World!"
}
Хорошо, мы разобрались что вычисляемое свойство возвращает значение своего типа, не хранится в памяти пока мы к нему не обратимся, и очень похоже на работу функции с возвращаемым значением.
Вычисляемые свойства, которые мы с вами рассмотрели выше называют геттерами, и они доступны только для чтения, то есть мы не сможем им присвоить другого значения.
var greeting: String {
"Hello, World!"
}
greeting = "Hello, Swift!"
// Ошибка: Cannot assign to value: 'greeting' is a get-only property
Вычисляемое свойство. Сеттер.
Что бы появилась возможность задавать новое значение для вычисляемого свойства, необходимо использовать следующий синтаксис:
var greeting: String {
get {
"Hello, World!"
}
set {
print("Hello, \(newValue)")
}
}
внутри фигурных скобок мы прописываем два блока, первый блок get, определяет то значение, которое возвращает вычисляемое свойство, по сути это то что мы рассмотрели в прошлой главе, отсюда и название геттер. А второй блок set, в нем мы будем определять что делать с входным значением.
И вот здесь вы можете заметить свойство интерполированное в строку – newValue, это системная константа, она нам дана из коробки в блоке set, это как раз то новое значение, которое мы передадим в наше вычисляемое свойство:
var greeting: String {
get {
"Hello, World!"
}
set {
print("Hello, \(newValue)")
}
}
greeting = "Swift!"
системное свойство newValue примет значение “Swift!” и в момент передачи этого значения, сработает блок set, и на консоли мы увидим следующий результат:
При этом свойство greeting все так же возвращает все что находится в блоке get, оно не переопределяется новым значением:
Грубо говоря, когда вы присваиваете новое значение свойству greeting, то срабатывает блок set, когда просто считываете это свойство, то срабатывает блок get. Любой из внутренних блоков get и set срабатывает в момент обращения к свойству.
Давайте загадаю вам маленькую задачку, внимательно посмотрите на следующий код:
var radius = 6.0
var diameter: Double {
get {
radius * 2
}
set {
radius = newValue
}
}
diameter = 2
diameter // Что вернет вычисляемое свойство?
Ответьте на вопрос, что произойдет в случае присвоения значения 2 и в случае простого обращения к свойству?
Так как мы присваиваем новое значение, то срабатывает блок set, а это значит что для свойства radius будет присвоено новое значение 2:
И когда мы следующим действием просто обращаемся к свойству diameter, срабатывает блок get, в котором мы берем радиус с новым значением и умножаем его на 2, таким образом мы получим результат 4:
Обдумайте этот момент.
Главное что вам нужно запомнить, это то что при простом обращении к вычисляемому свойству срабатывает блок get, в котором необходимо вернуть тот тип данных, которым является свойство. Если мы присваиваем новое значение, в этот момент срабатывает блок set, именно в момент присвоения нового значения.
Наблюдатели свойств.
В Swift наблюдатели свойств используются для отслеживания изменений значений свойств. Наблюдатели свойств могут быть добавлены для любых хранимых свойств.
Давайте создадим обычное хранимое свойство с типом String:
var name = ""
Что бы объявить наблюдателя, нужно поставить фигурные скобки сразу после значения и определить внутри один из блоков, либо два сразу willSet и didSet:
var name = "" {
willSet {
}
didSet {
}
}
willSet вызывается перед установкой нового значения, а didSet после установки нового значения. У каждого из этих блоков есть системные константы, у willSet свойство newValue, которое будет хранить новое значение, давайте выведем его на консоль:
var name = "" {
willSet {
print("Скоро изменится имя на \(newValue)")
}
didSet {
}
}
А у didSet системная константа называется oldValue, это предыдущее значение, в нашем случае это будет пустая строка, давайте в этом блоке выведем на консоль сообщение с этим значением:
var name = "" {
willSet {
print("Скоро изменится имя на \(newValue)")
}
didSet {
print("Было изменено имя с \(oldValue) на \(name)")
}
}
Вы можете заметить в блоке didSet обращение к самому свойству name, в этот момент оно уже будет содержать в себе новое значение и мы его отобразим на консоле.
Наблюдатели срабатывают только в тот момент, когда вы хотите изменить свойство, то есть присвоить новое значение, во всех остальных случаях они не реагируют:
var name = "" {
willSet {
print("Скоро изменится имя на \(newValue)")
}
didSet {
print("Было изменено имя с \(oldValue) на \(name)")
}
}
name // Ни один из принтов внутри блоков не сработает
Но как только мы присвоим новое значение, то отработает два блока сразу:
Так как изначально у нас имя было пустой строкой, то старое значение мы видим как дополнительный пробел, давайте присвоим новое значение для свойства name, а вы попробуете ответить на вопрос, как будет выглядеть фраза из блока didSet на консоле:
var name = "" {
willSet {
print("Скоро изменится имя на \(newValue)")
}
didSet {
print("Было изменено имя с \(oldValue) на \(name)")
}
}
name = "Tim"
name = "Swift"
Думаю вы догадались, что теперь старое значение будет Tim, а новое Swift, и на консоле мы увидим следующее: