Свойства
Доступ к хранимым и вычисляемым значениям, которые являются частью экземпляра или типа.
Свойства связывают значения с конкретным классом, структурой или перечислением. Хранимые свойства хранят постоянные и переменные значения в качестве части экземпляра, тогда как вычисляемые свойства вычисляют (а не хранят) значение. Вычисляемые свойства предоставляются классами, структурами и перечислениями. Хранимые свойства предоставляются только классами и структурами.
Хранимые и вычисляемые свойства обычно связаны с экземплярами конкретного типа. Однако свойства также могут быть связаны с самим типом. Такие свойства называются свойствами типа.
Кроме того, можно определить наблюдатели свойств для отслеживания изменений значения свойства, на которые можно реагировать с помощью пользовательских действий. Наблюдатели свойств можно добавить к хранимым свойствам, которые вы определяете сами, а также к свойствам, которые подкласс наследует от своего суперкласса.
Также можно использовать обертки свойств для повторного использования кода в методах получения и задания нескольких свойств.
Хранимые свойства
В простейшем виде хранимое свойство — это константа или переменная, которая хранится в качестве части экземпляра конкретного класса или структуры. Хранимые свойства могут быть либо переменными хранимыми свойствами (определяемыми ключевым словом var), либо постоянными хранимыми свойствами (определяемыми ключевым словом let).
Можно задать значение по умолчанию для хранимого свойства в его определении, как описано в <doc:Initialization#Default-Property-Values>. Также можно установить и изменить начальное значение хранимого свойства во время инициализации. Это верно даже для постоянных хранимых свойств, как описано в <doc:Initialization#Assigning-Constant-Properties-During-Initialization>.
В приведенном ниже примере определена структура под названием 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 hasn't yet been created Класс DataManager имеет хранимое свойство под названием data, которое инициализируется с помощью нового пустого массива значений String. Несмотря на то, что остальная часть его функциональности не показана, назначение этого класса DataManager — управление и предоставление доступа к этому массиву данных String.
Часть функциональности класса DataManager — возможность импорта данных из файла. Эта функциональность предоставляется классом DataImporter, инициализация которого, предположительно, занимает значительное время. Это может быть связано с тем, что экземпляру DataImporter требуется открыть файл и прочитать его содержимое в память при инициализации экземпляра DataImporter.
Поскольку экземпляр DataManager может управлять своими данными без импорта данных из файла, 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
// initialSquareCenter is at (5.0, 5.0)
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. Текущее положение центра Rect всегда можно определить по его свойствам origin и size, поэтому не нужно хранить точку центра как явное значение Point. Вместо этого Rect определяет пользовательские методы получения и задания для вычисляемой переменной center, чтобы можно было работать со свойством center прямоугольника, как будто это действительное хранимое свойство.
В приведенном выше примере создается новая переменная Rect под названием square. Переменная square инициализируется начальной точкой (0, 0) и шириной и высотой 10. Этот квадрат представлен светло-зеленым квадратом на диаграмме ниже.
Затем свойство square переменной center доступно через синтаксис точки (square.center), что вызывает вызов метода получения 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 из функции, как описано в <doc:Functions#Functions-With-an-Implicit-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 предоставить только для чтения вычисляемое свойство, чтобы внешние пользователи могли узнать текущий вычисленный объём.
Наблюдатели свойств
Наблюдатели свойств отслеживают и реагируют на изменения значения свойства. Наблюдатели свойств вызываются каждый раз, когда значение свойства устанавливается, даже если новое значение такое же, как текущее значение свойства.
Добавить наблюдатели свойств можно в следующих местах:
- Определенные вами хранимые свойства
- Унаследованные вами хранимые свойства
- Унаследованные вами вычисляемые свойства
Для унаследованного свойства вы добавляете наблюдатель свойства, переопределяя это свойство в подклассе. Для вычисляемого свойства, которое вы определяете, используйте установщик свойства для отслеживания и реагирования на изменения значения, вместо попытки создать наблюдатель. Переопределение свойств описано в <doc:Inheritance#Overriding>.
Вы можете определить один или оба из этих наблюдателей для свойства:
-
willSetвызывается непосредственно перед сохранением значения. -
didSetвызывается сразу после сохранения нового значения.
Если вы реализуете наблюдатель willSet, ему передаётся новое значение свойства как постоянный параметр. Вы можете указать имя для этого параметра как часть вашей реализации willSet. Если вы не напишете имя параметра и скобки в своей реализации, параметр будет доступен с именем параметра по умолчанию newValue.
Аналогично, если вы реализуете наблюдатель didSet, ему передаётся постоянный параметр, содержащий старое значение свойства. Вы можете назвать параметр или использовать имя параметра по умолчанию oldValue. Если вы присваиваете значение свойству внутри его собственного наблюдателя didSet, новое присваиваемое значение заменяет значение, которое только что было установлено.
Примечание: наблюдатели
willSetиdidSetсвойств суперкласса вызываются при установке свойства в инициализаторе подкласса после вызова инициализатора суперкласса. Они не вызываются, когда класс устанавливает свои собственные свойства до вызова инициализатора суперкласса.Дополнительную информацию об инициализации делегирования см. в <doc:Initialization#Initializer-Delegation-for-Value-Types> и <doc:Initialization#Initializer-Delegation-for-Class-Types>.
Вот пример работы 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.
Примечание: если вы передаёте свойство с наблюдателями в функцию как параметр in-out, наблюдатели
willSetиdidSetвсегда вызываются. Это связано с моделью памяти copy-in copy-out для параметров in-out: значение всегда записывается обратно в свойство в конце функции. Подробное обсуждение поведения параметров in-out см. в <doc:Declarations#In-Out-Parameters>.
Обертки свойств
Обёртка свойства добавляет слой разделения между кодом, управляющим тем, как хранится свойство, и кодом, определяющим свойство. Например, если у вас есть свойства, которые обеспечивают проверки потоковой безопасности или хранят свои исходные данные в базе данных, вам нужно писать этот код для каждого свойства. При использовании обёртки свойства вы пишете код управления один раз при определении обёртки, а затем повторно используете этот код управления, применяя его к нескольким свойствам.
Для определения обёртки свойства создайте структуру, перечисление или класс, который определяет свойство wrappedValue. В приведенном ниже коде структура TwelveOrLess гарантирует, что значение, которое она обертывает, всегда содержит число, меньшее или равное 12. Если вы попросите её сохранить большее число, она сохранит 12 вместо этого.
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
} Установщик гарантирует, что новые значения меньше или равны 12, а получатель возвращает сохранённое значение.
Примечание: объявление для
numberв примере выше помечает переменную какprivate, что гарантирует, чтоnumberиспользуется только в реализацииTwelveOrLess. Код, написанный где-либо ещё, получает доступ к значению с помощью получателя и установщика дляwrappedValueи не может напрямую использоватьnumber. Дополнительную информацию обprivateсм. в AccessControl.
Вы применяете обёртку к свойству, написав имя обёртки перед свойством в качестве атрибута. Вот структура, которая хранит прямоугольник, использующий обёртку свойства TwelveOrLess для обеспечения того, что его размеры всегда меньше или равны 12:
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12" Свойства height и width получают свои начальные значения из определения TwelveOrLess, которое устанавливает TwelveOrLess.number в ноль. Установщик в TwelveOrLess обрабатывает 10 как допустимое значение, поэтому сохранение числа 10 в rectangle.height происходит так, как написано. Однако, 24 больше, чем позволяет TwelveOrLess, поэтому попытка сохранить 24 в итоге устанавливает rectangle.height в 12, самое большое разрешённое значение.
При применении обёртки к свойству компилятор генерирует код, обеспечивающий хранение для обёртки и код, обеспечивающий доступ к свойству через обёртку. (Обёртка свойства отвечает за хранение обернутого значения, поэтому для этого нет сгенерированного кода.) Вы можете написать код, который использует поведение обёртки свойства, не используя специальный синтаксис атрибута. Например, вот версия SmallRectangle из предыдущего примера кода, которая явно обертывает свои свойства в структуру TwelveOrLess вместо написания @TwelveOrLess как атрибута:
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
} Свойства _height и _width хранят экземпляр обёртки свойства TwelveOrLess. Получатель и установщик для height и width обертывают доступ к свойству wrappedValue.
Установление начальных значений для обернутых свойств
Код в примерах выше устанавливает начальное значение для обернутого свойства, дав number начальное значение в определении TwelveOrLess. Код, использующий эту обёртку свойства, не может указать другое начальное значение для свойства, обернутого TwelveOrLess — например, определение SmallRectangle не может задать начальные значения для height или width. Для поддержки установки начального значения или других настроек обёртка свойства должна добавить инициализатор. Вот расширенная версия TwelveOrLess под названием SmallNumber, которая определяет инициализаторы, устанавливающие обернутое и максимальное значение:
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
} Определение SmallNumber включает три инициализатора — init(), init(wrappedValue:) и init(wrappedValue:maximum:) — которые примеры ниже используют для установки обернутого значения и максимального значения. Дополнительную информацию об инициализации и синтаксисе инициализаторов см. в Initialization.
При применении обёртки к свойству без указания начального значения Swift использует инициализатор init() для настройки обёртки. Например:
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0" Экземпляры SmallNumber, которые обертывают height и width, создаются путём вызова SmallNumber(). Код внутри этого инициализатора устанавливает начальное значение обернутого значения и начальное максимальное значение, используя значения по умолчанию ноль и 12. Обёртка свойства всё ещё предоставляет все начальные значения, как и в предыдущем примере, использующем TwelveOrLess в SmallRectangle. В отличие от этого примера, SmallNumber также поддерживает запись этих начальных значений при объявлении свойства.
При указании начального значения для свойства Swift использует инициализатор init(wrappedValue:) для настройки обёртки. Например:
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1" Когда вы пишете = 1 для свойства с обёрткой, это преобразуется в вызов инициализатора init(wrappedValue:). Экземпляры SmallNumber, которые обертывают height и width, создаются путём вызова SmallNumber(wrappedValue: 1). Инициализатор использует указанное здесь обернутое значение и значение по умолчанию максимального значения 12.
При написании аргументов в скобках после пользовательского атрибута Swift использует инициализатор, который принимает эти аргументы, для настройки обёртки. Например, если вы предоставите начальное значение и максимальное значение, Swift использует инициализатор init(wrappedValue:maximum:):
struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4" Экземпляр SmallNumber, который обертывает height, создаётся путём вызова SmallNumber(wrappedValue: 2, maximum: 5), а экземпляр, который обертывает width, создаётся путём вызова SmallNumber(wrappedValue: 3, maximum: 4).
Включение аргументов в обёртку свойства позволяет настроить начальное состояние в обёртке или передать другие параметры обёртке при её создании. Этот синтаксис — самый общий способ использования обёртки свойства. Вы можете передавать любые необходимые аргументы атрибуту, и они будут переданы в инициализатор.
Когда вы включаете аргументы обёртки свойства, вы также можете указать начальное значение с помощью присваивания. Swift рассматривает присваивание как аргумент wrappedValue и использует инициализатор, принимающий аргументы, которые вы включаете. Например:
struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12" Экземпляр SmallNumber, который оборачивает height, создаётся вызовом SmallNumber(wrappedValue: 1), использующим значение максимального порога 12 по умолчанию. Экземпляр, который оборачивает width, создаётся вызовом SmallNumber(wrappedValue: 2, maximum: 9).
Проектирование значения из обёртки свойства
Помимо обернутого значения, обёртка свойства может предоставлять дополнительную функциональность, определяя проектируемое значение — например, обёртка свойства, которая управляет доступом к базе данных, может экспонировать метод flushDatabaseConnection() в своём проектируемом значении. Имя проектируемого значения совпадает с именем обернутого значения, за исключением того, что оно начинается с символа доллара ($). Поскольку ваш код не может определять свойства, начинающиеся с $, проектируемое значение никогда не конфликтует со свойствами, которые вы определяете.
В примере SmallNumber выше, если вы попытаетесь установить свойство в число, слишком большое, обёртка свойства скорректирует число перед его сохранением. Код ниже добавляет свойство projectedValue в структуру SmallNumber, чтобы отслеживать, скорректировала ли обёртка свойства новое значение свойства перед его сохранением.
@propertyWrapper
struct SmallNumber {
private var number: Int
private(set) var projectedValue: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true" Запись someStructure.$someNumber обращается к проектируемому значению обёртки. После хранения малого числа, например четвёрки, значение someStructure.$someNumber равно false. Однако проектируемое значение равно true после попытки сохранить слишком большое число, например 55.
Обёртка свойства может возвращать значение любого типа в качестве своего проектируемого значения. В этом примере обёртка свойства экспонирует только одну единицу информации — скорректировано ли число — поэтому она экспонирует это булево значение в качестве своего проектируемого значения. Обёртка, которой нужно экспонировать больше информации, может возвращать экземпляр другого типа, или она может возвращать self, чтобы экспонировать экземпляр обёртки в качестве своего проектируемого значения.
Когда вы обращаетесь к проектируемому значению из кода, являющегося частью типа, например, из геттера свойства или метода экземпляра, вы можете опустить self. перед именем свойства, точно так же, как и при обращении к другим свойствам. Код в следующем примере ссылается на проектируемое значение обёртки вокруг height и width как на $height и $width:
enum Size {
case small, large
}
struct SizedRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
mutating func resize(to size: Size) -> Bool {
switch size {
case .small:
height = 10
width = 20
case .large:
height = 100
width = 100
}
return $height || $width
}
} Поскольку синтаксис обёртки свойства — просто синтаксический сахар для свойства с геттером и сеттером, обращение к height и width ведёт себя так же, как обращение к любому другому свойству. Например, код в resize(to:) обращается к height и width с помощью их обёртки свойства. Если вы вызываете resize(to: .large), оператор switch для .large устанавливает высоту и ширину прямоугольника в 100. Обёртка предотвращает, чтобы значение этих свойств было больше 12, и устанавливает проектируемое значение в true, чтобы зафиксировать тот факт, что она скорректировала их значения. В конце resize(to:) оператор return проверяет $height и $width, чтобы определить, скорректировала ли обёртка свойства значения height или width.
Глобальные и локальные переменные
Возможности, описанные выше для вычисления и наблюдения за свойствами, также доступны для глобальных переменных и локальных переменных. Глобальные переменные — это переменные, определённые вне любой функции, метода, замыкания или контекста типа. Локальные переменные — это переменные, определённые внутри функции, метода или контекста замыкания.
Все глобальные и локальные переменные, с которыми вы сталкивались в предыдущих главах, были хранимыми переменными. Хранимые переменные, как и хранимые свойства, обеспечивают хранение значения определённого типа и позволяют устанавливать и извлекать это значение.
Однако вы также можете определить вычисляемые переменные и определить наблюдатели для хранимых переменных в глобальном или локальном пространстве имён. Вычисляемые переменные вычисляют своё значение, а не хранят его, и они записываются так же, как и вычисляемые свойства.
Примечание: Глобальные константы и переменные всегда вычисляются лениво, аналогично <doc:Properties#Lazy-Stored-Properties>. В отличие от ленивых хранимых свойств, глобальные константы и переменные не нуждаются в указании модификатора
lazy.Локальные константы и переменные никогда не вычисляются лениво.
Вы можете применить обёртку свойства к локальной хранимой переменной, но не к глобальной переменной или вычисляемой переменной. Например, в коде ниже, myNumber использует SmallNumber в качестве обёртки свойства.
func someFunction() {
@SmallNumber var myNumber: Int = 0
myNumber = 10
// now myNumber is 10
myNumber = 24
// now myNumber is 12
} Так же, как при применении SmallNumber к свойству, установка значения myNumber в 10 допустима. Поскольку обёртка свойства не допускает значений больше 12, она устанавливает myNumber в 12, а не в 24.
Свойства типа
Свойства экземпляра — это свойства, которые относятся к экземпляру конкретного типа. Каждый раз, когда вы создаёте новый экземпляр этого типа, он имеет свой набор значений свойств, отдельный от любого другого экземпляра.
Вы также можете определить свойства, которые относятся к самому типу, а не к какому-либо одному экземпляру этого типа. Будет только одна копия этих свойств, независимо от того, сколько экземпляров этого типа вы создадите. Такие свойства называются свойствами типа.
Свойства типа полезны для определения значений, универсальных для всех экземпляров конкретного типа, например, постоянного свойства, которое могут использовать все экземпляры (как статическая константа в 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 обновляется и становится равным 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"
This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception