Свойства связывают значения с определенным классом, структурой или перечислением. Сохраненные свойства хранят постоянные и переменные значения как часть экземпляра, тогда как вычисленные свойства вычисляют (а не сохраняют) значение. Вычисляемые свойства предоставляются классами, структурами и перечислениями. Сохраненные свойства предоставляются только классами и структурами.

Сохраненные и вычисляемые свойства обычно связаны с экземплярами определенного типа. Однако свойства также могут быть связаны с самим типом. Такие свойства известны как свойства типа.

Кроме того, вы можете определить наблюдателей свойств для отслеживания изменений в значении свойства, на которые вы можете реагировать с помощью пользовательских действий. Наблюдатели свойств могут быть добавлены к сохраненным свойствам, которые вы определяете сами, а также к свойствам, которые подкласс наследует от своего суперкласса.

Сохраненные Свойства

В своей простейшей форме хранимое свойство - это константа или переменная, которая хранится как часть экземпляра определенного класса или структуры. Хранимые свойства могут быть либо переменными хранимыми свойствами (вводится 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переменная с именем squaresquareПеременная инициализируется с точкой происхождения , а также ширины и высоты . Этот квадрат представлен синим квадратом на диаграмме ниже.(0, 0)10

Затем squareк centerсвойству переменной получают доступ через синтаксис точки ( square.center), который вызывает метод get для centerполучения текущего значения свойства. Вместо того, чтобы возвращать существующее значение, получатель фактически вычисляет и возвращает новое, Pointчтобы представить центр квадрата. Как видно выше, геттер правильно возвращает центральную точку .(5, 5)

Затем для centerсвойства устанавливается новое значение , которое перемещает квадрат вверх и вправо, в новое положение, показанное оранжевым квадратом на диаграмме ниже. Установка свойства вызывает Присваиватель , который изменяет и значение хранимого имущества, и перемещает квадрат в новое положение.(15, 15)centercenterxyorigin

Сокращенное сеттерное объявление

Если установщик вычисляемого свойства не определяет имя для нового устанавливаемого значения, используется имя по умолчанию 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 прямоугольную форму с widthheightи depthсвойствами. Эта структура также имеет вычисляемое свойство volume, предназначенное только для чтения , которое называется , которое вычисляет и возвращает текущий объем кубоида. Это не имеет смысла для volumeбыть настраиваемыми, потому что это будет неоднозначным, какие значения widthheightи 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"