Приветствую на лекции, посвященной свойствам или properties.
Свойства связывают значения с конкретным классом, структурой или перечислением.
Различают хранимые и вычисляемые свойства.
Хранимые свойства запоминают свое значение константы или переменной, в то время
как вычисляемые свойства рассчитывают значение при каждом обращении.
Вычисляемые свойства доступны классам,
структурам и перечислениям, а хранимые, только классам и структурам.
Хранимые и вычисляемые свойства обычно привязываются к инстансам.
Но свойства могут быть связаны и непосредственно с типом.
Такие свойства называются свойствами типов.
Так же для хранимых свойств можно определить обозревателей.
Они будут наблюдать за изменением значения свойства,
а при изменении выполнять дополнительные действия.
В простейшем понимании, хранимое свойство — это константа или переменная,
принадлежащая инстансу.
Для свойства можно указывать значения по умолчанию или присвоить значения в
инициализаторе.
В данном примере мы объявляем структуру с
переменной типа интеджер, которой первоначально присвоено значение 1,
а также с константой, значение которой будет передано в инициализатор.
Значение переменной мы можем изменять уже после создания структуры.
Значение константы же изменить уже не получится.
Если мы объявим структуру и присвоим ее значение
константе, то значение у данной структуры поменять уже не получится.
Попробуем сделать это.
Создадим ту же самую структуру, но присвоим ее константе.
Как видите, мы получаем ошибку компиляции,
когда пытаемся переприсвоить значение переменной.
Даже несмотря на то, что внутри структуры она объявлена как var.
Связано это с тем, что структуры являются значимыми типами.
Если инстанс-структуры помечаются как константы,
то и свойства структуры будут константными.
Для классов поведение отличается, так как классы — это ссылочный тип.
При присвоении инстанса класса константе, значения свойств,
объявленных как переменные, могут быть изменены.
Нельзя лишь переприсвоить данной константе другой объект.
Отложенная инициализация позволяет установить значения для свойств в момент
первого обращения.
Такие свойства помечаются ключевым словом lazy.
В данном примере мы
создаем класс dataStore с одной переменной,
а также класс dataManager, у которого есть одна переменная — dataStore.
Она объявлена как lazy, соответственно, когда мы создаем константу с dataManager,
обращения к инициализатору класса dataStore не будет происходить.
Инициализация будет выполнена лишь в тот момент,
когда мы впервые обратимся к переменой dataStore.
То есть в данной строке.
Когда мы обращаемся к той же самой переменной из следующей строки,
мы будем иметь дело с тем же самым объектом,
который был проинициализирован строкой ранее.
Свойства с отложенной инициализацией всегда объявляются как переменные, ведь
значение подобного свойства может быть определено после инициализации объекта,
а для констант это непозволительно.
Использовать подобные свойства удобно, если их значения зависят от других и
не могут быть определены на этапе создания объекта.
Также отложенную инициализацию стоит применять для свойств,
чье создание может занять продолжительное время или потребовать много ресурсов.
Помимо хранимых свойств могут быть определены и вычисляемые.
Они не хранят определенные значения,
а вычисляют его при каждом обращении к переменной.
В данном примере мы объединяем класс с одной хранимой
переменной — numbers, а также с вычисляемой переменной counter,
геттер которой возвращает количество элементов в массиве numbers,
а сеттер выставляет новые элементы в данный массив.
При выставлении переменной counter будет выполнен код,
который находится после ключевого слова set.
Когда мы печатаем значения переменной counter, будет выполнен код геттера,
то есть получение количества элементов из переменной numbers.
Если у вычисляемого свойства в сеттере не задается имя для нового значения,
то оно задается по умолчанию и называется new value.
Имена передаваемых переменных также можно назначить самостоятельно.
Когда для вычисляемого свойства устанавливается геттер,
но не устанавливается сеттер, оно доступно только для чтения.
Значение вычисляемого свойства устанавливается как переменная,
а не константа, так как значение не является фиксированным.
При создании вычисляемого свойства только с геттером,
синтаксис можно немножко упростить и убрать ключевое слово get.
Свойства следят за своими значениями и могут сообщать об этом.
Обозреватели свойств вызываются при установке значения,
даже если значение не изменилось.
Обозреватели можно использовать для любых хранимых свойств,
кроме свойств с отложенной инициализацией.
Также обозреватели могут быть добавлены для унаследованных свойств.
Для обозревания свойств необходимо использовать ключевые слова willSet и
didSet.
Рассмотрим пример.
Мы создаем класс Explicit Counter, у которого есть одна хранимая переменная
total, а также объявляются обзерверы: willSet, который печатает в консоль,
что скоро установится новое значение, и didSet, который печатает в консоль,
на сколько изменилось новое значение, если оно больше предыдущего.
Далее мы можем видеть пример использования данного класса: сначала создаем инстанс,
а потом присваиваем переменной total значение 5.
Как видите, мы видим результат выполнения обоих обзерверов.
Когда же мы присваиваем новое значение 9, то выполняется метод willSet,
который печатает, что новое значение 9 скоро будет установлено,
а после установки нового значения выполняется didSet, который говорит нам,
что новое значение изменилось на 4.
WillSet вызывается непосредственно перед изменением свойства,
а didSet сразу после изменения.
В willSet передается константное значение.
Для значения может быть использовано уникальное имя, если оно не установлено,
значение будет с именем по умолчанию newValue.
Для didSet также передается старое значение,
но оно имеет другое имя по умолчанию — oldValue.
Если внутри didSet установить новое значение наблюдаемой переменной,
то оно заменит только что установленное.
Когда в свойства с обозревателем передается функция в качестве in out
параметра, у свойства будут вызваны методы willSet и didSet,
даже если значение не было переприсвоено.
Это связано с особенностью реализации in out параметров.
При завершении функции всегда записываются новые значения и, соответственно,
вызываются обозреватели.
Ранее описанные возможности доступны для глобальных и локальных переменных.
Глобальные переменные задаются вне метода функции или класса,
локальные же переменные объявляются внутри функции или замыкания.
Для локальных и глобальных переменных можно задать обозреватели и сделать
переменные вычисляемыми.
Все точно так же, как и для свойств.
Глобальные переменные и константы всегда вычисляются при обращении.
В отличие от свойств,
глобальные переменные не нужно помечать ключевым словом lazy.
Локальные переменные не могут обладать отложенной инициализацией.
Теперь поговорим о свойствах для типов.
Свойства объектов принадлежат к объектам и создаются каждый раз при
инициализации нового объекта.
Но можно создать свойство, которое будет единственным для всех объектов заданного
типа, и значение которого будет инициализировано только один раз.
Такие свойства называются свойствами типов.
Они аналогичны переменным и константам из языка C,
которые объявлены с ключевым словом static.
Вычисляемые свойства типов могут быть объявлены только как переменные.
Хранимые же свойства типов могут быть объявлены и как переменные,
и как константы.
В отличие от свойств у инстансов, для переменных обязательно указание значения
по умолчанию, так как инициализаторов у них нет, соответственно,
и нет возможности для задания первоначального значения.
Хранимые свойства получают значения при первом обращении к ним.
Это происходит только один раз.
В swift свойства типов описываются внутри этого самого типа.
Каждое свойство привязано к типу, в котором оно описано.
В данном примере мы создаем структуру с двумя свойствами
типа: одним хранимым и другим вычисляемым, который зависит от хранимого.
Также описываем класс, в котором три свойства типа: одно хранимое,
другое вычисляемое, которое зависит от хранимого, и третье вычисляемое,
но объявленное с ключевым словом class.
Объявление свойства типа при помощи ключевого
слова class позволяет нам переопределить данное свойство при наследовании.
Обращение к свойствам типов происходит через Dot syntax,
только вместо инстанс объекта мы указываем сам тип.
На этом заканчивается наш урок, посвященный свойствам,
обозревателям свойств и работе с ними.