Свойства связывают значения с определенным классом, структурой или перечислением. Сохраненные свойства хранят постоянные и переменные значения как часть экземпляра, тогда как вычисленные свойства вычисляют (а не сохраняют) значение. Вычисляемые свойства предоставляются классами, структурами и перечислениями. Сохраненные свойства предоставляются только классами и структурами.
Сохраненные и вычисляемые свойства обычно связаны с экземплярами определенного типа. Однако свойства также могут быть связаны с самим типом. Такие свойства известны как свойства типа.
Кроме того, вы можете определить наблюдателей свойств для отслеживания изменений в значении свойства, на которые вы можете реагировать с помощью пользовательских действий. Наблюдатели свойств могут быть добавлены к сохраненным свойствам, которые вы определяете сами, а также к свойствам, которые подкласс наследует от своего суперкласса.
Сохраненные Свойства
В своей простейшей форме хранимое свойство - это константа или переменная, которая хранится как часть экземпляра определенного класса или структуры. Хранимые свойства могут быть либо переменными хранимыми свойствами (вводится var
ключевым словом), либо постоянными хранимыми свойствами (вводится let
ключевым словом)
Вы можете предоставить значение по умолчанию для хранимого свойства как часть его определения, как описано в разделе Значения свойств по умолчанию . Вы также можете установить и изменить начальное значение для сохраненного свойства во время инициализации. Это верно даже для постоянных сохраненных свойств, как описано в разделе «Назначение постоянных свойств во время инициализации» .
В приведенном ниже примере определяется структура с именем FixedLengthRange
, которая описывает диапазон целых чисел, длина диапазона которых не может быть изменена после его создания:
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8
Экземпляры FixedLengthRange
имеют переменное хранимое свойство с именем firstValue
и постоянное хранимое свойство с именем length
. В приведенном выше примере length
инициализируется при создании нового диапазона и не может быть изменен после этого, потому что это постоянное свойство.
Хранимые свойства экземпляров постоянной структуры
Если вы создаете экземпляр структуры и назначаете этот экземпляр константе, вы не сможете изменить свойства экземпляра, даже если они были объявлены как свойства переменной:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property
Поскольку rangeOfFourItems
объявляется как константа (с let
ключевым словом), невозможно изменить ее firstValue
свойство, даже если firstValue
это свойство переменной.
Такое поведение обусловлено тем, что структуры являются типами значений . Когда экземпляр типа значения помечается как константа, то же относится и ко всем его свойствам.
То же самое не относится к классам, которые являются ссылочными типами . Если вы назначите экземпляр ссылочного типа константе, вы все равно можете изменить свойства переменной этого экземпляра.
Ленивые Хранящиеся Свойства
Ленивым хранится свойство является свойством которого начальное значение не вычисляется до тех пор , впервые она используется. Вы указываете ленивое сохраненное свойство, написав lazy
модификатор перед его объявлением.
ЗАМЕТКА
Вы всегда должны объявлять ленивое свойство как переменную (с
var
ключевым словом), потому что его начальное значение не может быть получено до завершения инициализации экземпляра. Постоянные свойства всегда должны иметь значение до завершения инициализации, и поэтому не могут быть объявлены как ленивые.
Ленивые свойства полезны, когда начальное значение свойства зависит от внешних факторов, значения которых неизвестны до завершения инициализации экземпляра. Ленивые свойства также полезны, когда для начального значения свойства требуется сложная или дорогостоящая в вычислительном отношении настройка, которую не следует выполнять до тех пор, пока она не потребуется.
В приведенном ниже примере используется отложенное хранимое свойство, чтобы избежать ненужной инициализации сложного класса. Этот пример определяет два вызываемых класса DataImporter
и DataManager
ни один из которых не показан полностью:
class DataImporter {
/*
DataImporter is a class to import data from an external file.
The class is assumed to take a nontrivial amount of time to initialize.
*/
var filename = "data.txt"
// the DataImporter class would provide data importing functionality here
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// the DataManager class would provide data management functionality here
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// the DataImporter instance for the importer property has not yet been created
DataManager
Класс имеет сохраненное свойство data
, которое инициализируется с новым, пустым массивом String
значений. Хотя остальные его функциональные возможности не показаны, целью этого DataManager
класса является управление и предоставление доступа к этому массиву String
данных.
Частью функциональности DataManager
класса является возможность импорта данных из файла. Эта функциональность обеспечивается DataImporter
классом, который, как предполагается, занимает нетривиальное количество времени для инициализации. Это может быть связано с тем, что DataImporter
экземпляру необходимо открыть файл и прочитать его содержимое в память при DataImporter
инициализации экземпляра.
Для DataManager
экземпляра возможно управлять своими данными, даже не импортируя данные из файла, поэтому нет необходимости создавать новый DataImporter
экземпляр при создании DataManager
самого. Вместо этого имеет смысл создавать DataImporter
экземпляр, если и когда он впервые используется.
Поскольку он помечен lazy
модификатором, DataImporter
экземпляр importer
свойства создается только при importer
первом обращении к свойству, например, когда filename
запрашивается его свойство:
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
ЗАМЕТКА
Если свойство, помеченное
lazy
модификатором, доступно нескольким потокам одновременно, и свойство еще не было инициализировано, нет гарантии, что свойство будет инициализировано только один раз.
Хранимые свойства и переменные экземпляра
Если у вас есть опыт работы с Objective-C, вы можете знать, что он предоставляет дваспособа хранения значений и ссылок как части экземпляра класса. Помимо свойств, вы можете использовать переменные экземпляра в качестве резервного хранилища для значений, хранящихся в свойстве.
Swift объединяет эти концепции в единую декларацию свойства. Свойство Swift не имеет соответствующей переменной экземпляра, и резервное хранилище для свойства не доступно напрямую. Этот подход позволяет избежать путаницы в отношении доступа к значению в разных контекстах и упрощает объявление свойства в единый, определенный оператор. Вся информация о свойстве, включая его имя, тип и характеристики управления памятью, определяется в одном месте как часть определения типа.
Вычисленные свойства
В дополнение к сохраненным свойствам классы, структуры и перечисления могут определять вычисляемые свойства , которые на самом деле не хранят значения. Вместо этого они предоставляют метод получения и дополнительный набор для косвенного извлечения и установки других свойств и значений.
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"
Этот пример определяет три структуры для работы с геометрическими фигурами:
Point
инкапсулирует координаты x и y точки.Size
инкапсулирует аwidth
и аheight
.Rect
определяет прямоугольник исходной точкой и размером.
Rect
Структура также обеспечивает вычисленное свойство center
. Текущее положение центра a Rect
всегда можно определить по его origin
и size
, поэтому вам не нужно сохранять центральную точку как явное Point
значение. Вместо этого Rect
определяется пользовательский метод получения и установки для вычисляемой переменной, которая называется center
, чтобы позволить вам работать с прямоугольником, center
как если бы он был реальным сохраненным свойством.
В приведенном выше примере создается новая Rect
переменная с именем square
. square
Переменная инициализируется с точкой происхождения , а также ширины и высоты . Этот квадрат представлен синим квадратом на диаграмме ниже.(0, 0)
10
Затем square
к center
свойству переменной получают доступ через синтаксис точки ( square.center
), который вызывает метод get для center
получения текущего значения свойства. Вместо того, чтобы возвращать существующее значение, получатель фактически вычисляет и возвращает новое, Point
чтобы представить центр квадрата. Как видно выше, геттер правильно возвращает центральную точку .(5, 5)
Затем для center
свойства устанавливается новое значение , которое перемещает квадрат вверх и вправо, в новое положение, показанное оранжевым квадратом на диаграмме ниже. Установка свойства вызывает Присваиватель , который изменяет и значение хранимого имущества, и перемещает квадрат в новое положение.(15, 15)
center
center
x
y
origin
Сокращенное сеттерное объявление
Если установщик вычисляемого свойства не определяет имя для нового устанавливаемого значения, используется имя по умолчанию newValue
. Вот альтернативная версия Rect
структуры, которая использует эту сокращенную запись:
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
Сокращенная декларация получателя
Если все тело получателя является одним выражением, получатель неявно возвращает это выражение. Вот еще одна версия Rect
структуры, которая использует эту сокращенную запись и сокращенную запись для сеттеров:
struct CompactRect {
var origin = Point()
var size = Size()
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
Отказ return
от метода получения следует тем же правилам, что и return
для функции, как описано в разделе Функции с неявным возвратом .
Вычисляемые свойства только для чтения
Вычисляемое свойство с получателем, но без установщика, известно как вычисляемое свойство только для чтения . Только для чтения вычисляемое свойство всегда возвращает значение и может быть доступно через точечный синтаксис, но не может быть установлено другое значение.
ЗАМЕТКА
Вы должны объявить вычисленные свойства - включая вычисляемые свойства только для чтения - как переменные свойства с
var
ключевым словом, потому что их значение не является фиксированным.let
Ключевое слово используется только для постоянных свойств, чтобы указать , что их значения не могут быть изменены , когда они устанавливаются как часть инициализации экземпляра.
Вы можете упростить объявление вычисляемого свойства только для чтения, удалив get
ключевое слово и его фигурные скобки:
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"
Этот пример определяет новую структуру , называемую Cuboid
, которая представляет собой 3D прямоугольную форму с width
, height
и depth
свойствами. Эта структура также имеет вычисляемое свойство volume
, предназначенное только для чтения , которое называется , которое вычисляет и возвращает текущий объем кубоида. Это не имеет смысла для volume
быть настраиваемыми, потому что это будет неоднозначным, какие значения width
, height
и depth
должно быть использовано для конкретного volume
значения. Тем не менее, полезно Cuboid
предоставить вычисляемое свойство только для чтения, чтобы внешние пользователи могли обнаружить его текущий вычисленный объем.
Наблюдатели за недвижимостью
Наблюдатели за недвижимостью наблюдают и реагируют на изменения стоимости имущества. Наблюдатели свойств вызываются каждый раз, когда устанавливается значение свойства, даже если новое значение совпадает с текущим значением свойства.
Вы можете добавить наблюдатели свойств к любым сохраненным свойствам, которые вы определяете, кроме отложенных сохраненных свойств. Вы также можете добавить наблюдатели свойств к любому унаследованному свойству (сохраненному или вычисленному), переопределив свойство в подклассе. Вам не нужно определять наблюдатели свойств для не переопределенных вычисляемых свойств, потому что вы можете наблюдать и реагировать на изменения их значения в установщике вычисляемого свойства. Переопределение свойств описано в Переопределении .
У вас есть возможность определить одного или обоих из этих наблюдателей для свойства:
willSet
вызывается непосредственно перед сохранением значения.didSet
вызывается сразу после сохранения нового значения.
Если вы реализуете willSet
наблюдателя, ему передается новое значение свойства в качестве постоянного параметра. Вы можете указать имя для этого параметра как часть вашей willSet
реализации. Если вы не пишете имя параметра и круглые скобки в вашей реализации, параметр становится доступным с именем параметра по умолчанию - newValue
.
Аналогично, если вы реализуете didSet
наблюдателя, ему передается постоянный параметр, содержащий старое значение свойства. Вы можете назвать параметр или использовать имя параметра по умолчанию oldValue
. Если вы присваиваете значение свойству в своем собственном didSet
наблюдателе, новое назначенное вами значение заменяет то, которое было только что установлено.
ЗАМЕТКА
willSet
ИdidSet
наблюдатели от суперкласса свойств вызывается , когда свойство устанавливается в подклассе инициализаторе, после того , как суперкласс инициализатор был вызван. Они не вызываются, пока класс устанавливает свои собственные свойства до вызова инициализатора суперкласса.
Для получения дополнительной информации о делегировании инициализатора см. Делегирование инициализатора для типов значений и Делегирование инициализатора для типов классов .
Вот пример willSet
и didSet
в действии. Пример ниже определяет новый класс с именем StepCounter
, который отслеживает общее количество шагов, которые человек делает во время ходьбы. Этот класс может использоваться с входными данными с шагомера или другого счетчика шагов, чтобы отслеживать упражнения человека во время его повседневной жизни.
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
StepCounter
Класс объявляет totalSteps
свойство типа Int
. Это свойство сохраняется с willSet
и didSet
наблюдателями.
willSet
И didSet
наблюдатели totalSteps
называют всякий раз , когда свойство присваивается новое значение. Это верно, даже если новое значение совпадает с текущим значением.
willSet
Наблюдатель этого примера использует имя настраиваемого параметра newTotalSteps
для предстоящего нового значения. В этом примере он просто выводит значение, которое должно быть установлено.
didSet
Наблюдатель вызывается после значения totalSteps
обновляется. Он сравнивает новое значение totalSteps
со старым значением. Если общее количество шагов увеличилось, печатается сообщение, указывающее, сколько новых шагов было сделано. didSet
Наблюдатель не обеспечивает пользовательское имя параметра для старого значения, и имя по умолчанию oldValue
используются вместо этого.
ЗАМЕТКА
Если вы передаете свойство, у которого есть наблюдатели, в функцию в качестве входного-выходного параметра, всегда вызывается наблюдатель
willSet
иdidSet
. Это связано с моделью памяти для копирования и вывода для параметров ввода-вывода: значение всегда записывается обратно в свойство в конце функции. Для подробного обсуждения поведения входных-выходных параметров см. Входные-выходные параметры .
Глобальные и локальные переменные
Описанные выше возможности для вычисления и наблюдения свойств также доступны для глобальных переменных и локальных переменных . Глобальные переменные - это переменные, которые определены вне какой-либо функции, метода, замыкания или типа контекста. Локальные переменные - это переменные, которые определены в контексте функции, метода или замыкания.
Глобальные и локальные переменные, с которыми вы столкнулись в предыдущих главах, были сохраненными переменными . Хранимые переменные, такие как хранимые свойства, предоставляют хранилище для значения определенного типа и позволяют устанавливать и получать это значение.
Однако вы также можете определить вычисляемые переменные и определить наблюдателей для хранимых переменных в глобальной или локальной области. Вычисляемые переменные вычисляют свое значение, а не сохраняют его, и они записываются так же, как и вычисляемые свойства.
ЗАМЕТКА
Глобальные константы и переменные всегда вычисляются лениво, аналогично Lazy Stored Properties . В отличие от отложенных хранимых свойств, глобальные константы и переменные не должны быть отмечены lazy
модификатором.
Локальные константы и переменные никогда не вычисляются лениво.
Тип недвижимости
Свойства экземпляра - это свойства, которые принадлежат экземпляру определенного типа. Каждый раз, когда вы создаете новый экземпляр этого типа, он имеет свой собственный набор значений свойств, отдельный от любого другого экземпляра.
Вы также можете определить свойства, которые принадлежат самому типу, а не одному экземпляру этого типа. Всегда будет только одна копия этих свойств, независимо от того, сколько экземпляров этого типа вы создадите. Эти виды свойств называются свойствами типа .
Свойства типа полезны для определения значений, которые являются универсальными для всех экземпляров определенного типа, таких как свойство константы, которое могут использовать все экземпляры (например, статическая константа в C), или свойство переменной, которое хранит значение, глобальное для всех экземпляры этого типа (как статическая переменная в C).
Свойства хранимого типа могут быть переменными или константами. Свойства вычисляемого типа всегда объявляются как свойства переменной, так же, как свойства вычисляемого экземпляра.
ЗАМЕТКА
В отличие от свойств хранимых экземпляров, вы всегда должны давать свойствам хранимого типа значение по умолчанию. Это связано с тем, что сам тип не имеет инициализатора, который может присвоить значение сохраненному свойству типа во время инициализации.
Свойства хранимого типа лениво инициализируются при первом доступе. Они гарантированно инициализируются только один раз, даже при одновременном доступе к нескольким потокам, и их не нужно отмечать lazy
модификатором.
Синтаксис свойства типа
В C и Objective-C вы определяете статические константы и переменные, связанные с типом, как глобальные статические переменные. Однако в Swift свойства типа записываются как часть определения типа во внешних фигурных скобках типа, и каждое свойство типа явно ограничено поддерживаемым типом.
Вы определяете свойства типа с помощью static
ключевого слова. Для свойств вычисляемых типов для типов классов class
вместо этого можно использовать ключевое слово, чтобы подклассы могли переопределить реализацию суперкласса. В приведенном ниже примере показан синтаксис для хранимых и вычисляемых свойств типа:
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
ЗАМЕТКА
Приведенные выше примеры свойств вычисляемого типа предназначены для свойств вычисляемого типа только для чтения, но вы также можете определить свойства вычисляемого типа для чтения и записи с тем же синтаксисом, что и для свойств вычисляемого экземпляра.
Запрос и настройка свойств типа
Свойства типа запрашиваются и устанавливаются с точечным синтаксисом, как и свойства экземпляра. Однако свойства типа запрашиваются и устанавливаются для типа , а не для экземпляра этого типа. Например:
print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"
В следующих примерах используются два свойства сохраненных типов как часть структуры, которая моделирует измеритель уровня звука для ряда аудиоканалов. Каждый канал имеет целочисленный уровень звука между 0
и 10
включительно.
На рисунке ниже показано, как два из этих аудиоканалов можно объединить для моделирования измерителя уровня стереозвука. Когда уровень звука в канале равен 0
, ни один из индикаторов этого канала не горит. Когда уровень звука равен 10
, все индикаторы этого канала горят. На этом рисунке левый канал имеет текущий уровень 9
, а правый канал имеет текущий уровень 7.
Аудиоканалы, описанные выше, представлены экземплярами AudioChannel
структуры:
struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.thresholdLevel {
// cap the new audio level to the threshold level
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
// store this as the new overall maximum input level
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
AudioChannel
Структура определяет две хранимых свойства типа для поддержки его функциональности. Первое, thresholdLevel
определяет максимальное пороговое значение, которое может принять уровень звука. Это постоянное значение 10
для всех AudioChannel
случаев. Если аудиосигнал поступает с более высоким значением, чем 10
, он будет ограничен этим пороговым значением (как описано ниже).
Второе свойство типа - это хранимое свойство переменной maxInputLevelForAllChannels
. Это отслеживает максимальное входное значение, которое было получено любым AudioChannel
экземпляром. Начинается с начального значения 0
.
AudioChannel
Структура также определяет хранимое свойство экземпляра currentLevel
, который представляет текущий уровень звукового канала на шкале 0
с 10
.
currentLevel
Свойство имеет didSet
свойство наблюдателя , чтобы проверить значение , currentLevel
когда оно установлено. Этот наблюдатель выполняет две проверки:
- Если новое значение
currentLevel
больше , чем разрешеноthresholdLevel
, то свойство наблюдателя колпачковcurrentLevel
вthresholdLevel
. - Если новое значение
currentLevel
(после какого-либо ограничения) больше, чем какое-либо значение, ранее полученное любымAudioChannel
экземпляром, наблюдатель свойства сохраняет новоеcurrentLevel
значение вmaxInputLevelForAllChannels
свойстве типа.
ЗАМЕТКА
В первой из этих двух проверок
didSet
наблюдатель устанавливаетcurrentLevel
другое значение. Это, однако, не вызывает повторного вызова наблюдателя.
Вы можете использовать AudioChannel
структуру для создания двух новых аудиоканалов, называемых leftChannel
и rightChannel
, для представления уровней звука стереофонической звуковой системы:
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
Если вы установите currentLevel
для левого канала значение 7
, вы увидите, что maxInputLevelForAllChannels
свойство type обновлено до равного 7
:
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
Если вы попытаетесь установить currentLevel
для правого канала значение 11
, вы увидите, что currentLevel
свойство правого канала ограничено до максимального значения 10
, а maxInputLevelForAllChannels
свойство типа обновлено до равного 10
:
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"
0 комментариев