Property associates a value with a particular class, structure, or enumeration. A storage property stores constants or variables as part of an instance, while a computational property evaluates (rather than stores) a value. Computed attributes can be used for classes, structures, and enumerations, while stored attributes can only be used for classes and structures.
Storage properties and computational properties are often associated with instances of a particular type. However, attributes can also act directly on the type itself, and are called type attributes.
In addition, you can trigger a custom action by defining a property observer to monitor changes in property values. Property watchers can be added to self-defined storage properties or to properties inherited from a parent class.
Simply put, a storage property is a constant or variable stored in an instance of a particular class or structure. The storage property can be a variable storage property (defined with the keyword var) or a constant storage property (defined with the keyword let).
You can specify default values when defining the storage properties. You can also set or modify the value of the store property during construction, or even modify the value of the constant store property.
The following example defines a structure called FixedLengthRange that describes the range of integers, and the range value cannot be modified after being created.
struct FixedLengthRange { var firstValue: Int let length: Int } var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3) / / said between the integer 0 rangeOfThreeItems. FirstValue = 6 / / the area between said integer June nowCopy the code
An instance of FixedLengthRange contains a variable storage property called firstValue and a constant storage property called length. In the example above, length is initialized when the instance is created, and because it is a constant storage property, its value cannot be modified later.
The storage property of a constant structure
If you create an instance of a structure and assign it to a constant, you cannot modify any of its properties, even if any of the properties are declared as variables:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4) / / the area said an integer between 0,1,2,3 rangeOfFourItems. FirstValue = 6 / / although firstValue is a variable attributes, there will still be an errorCopy the code
Because rangeOfFourItems is declared as a constant (with the let keyword), even if firstValue is a variable attribute, it cannot be modified.
This behavior is due to the fact that structs are value types. When an instance of a value type is declared a constant, all of its properties become constants as well. Classes that belong to reference types are different. After assigning an instance of a reference type to a constant, you can still modify the variable properties of that instance.
Delayed storage property
Deferred storage properties are properties whose initial values are computed when they are first called. Use lazy to indicate a deferred storage property before the property declaration.
Note that you must declare deferred storage properties as variables (using the var keyword) because the initial value of the property may not be available until after the instance is constructed. A constant property must have an initial value before the construction process is complete and therefore cannot be declared as a delayed property.
Deferred properties are useful when the value of the property depends on external factors that will affect the value until after the construction of the instance is complete, or when getting the initial value of the property requires complex or extensive computation, you can only compute it when needed.
The following example uses deferred storage properties to avoid unnecessary initialization in complex classes. DataImporter and DataManager classes are defined in this example. The following is part of the code:
Class DataImporter {/* DataImporter is a class responsible for importing data from external files. The initialization of this class will take some time. } class DataManager {lazy var importer = DataImporter() var data = } let manager = DataManager() manager.data.append("Some data") Manager.data.append ("Some more ") The importer property of the DataImporter instance has not been createdCopy the code
The DataManager class contains a storage property called data, whose initial value is an empty array of strings. The full code is not given here, except that the purpose of the DataManager class is to manage and provide access to this array of strings.
One of the functions of DataManager is to import data from files. This functionality is provided by the DataImporter class, which takes quite a bit of time to initialize: its instance may have to open a file and read the file contents into memory while initializing.
DataManager may also manage data without importing data from files. So when an instance of DataManager is created, it is not necessary to create an instance of DataImporter. It is wiser to create DataImporter when it is first used.
Because of the use of lazy, the importer property is created only the first time it is accessed. For example, when accessing its property fileName:
Print (manager) importer) fileName) / / DataImporter instance attributes of the importer is now being created / / output data. TXT ""Copy the code
Note that if a property marked lazy is accessed by multiple threads at the same time without being initialized, there is no guarantee that the property will be initialized only once.
Store properties and instance variables
If you have any experience with Objective-C, you should know that Objective-C provides two ways to store values and references for class instances. In addition to properties, you can also use instance variables as back-end storage for property values.
In Swift programming language, these theories are implemented with attributes. Attributes in Swift do not have corresponding instance variables, and the backend store for attributes is not directly accessible. This eliminates the hassles of different scenarios and reduces the definition of attributes to a single statement. All the information for an attribute — including its name, type, and memory management characteristics — is defined in a single place (the type definition).
Calculate attribute
In addition to storing properties, classes, structs, and enumerations can define computed properties, which do not store values directly, but instead provide a getter and an optional setter to indirectly get and set the values of other properties or variables.
Struct Point {var x = 0.0, y = 0.0} struct Size {var width = 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: 0), 0)) let initialSquareCenter = square. Center square. Center = Point(x: 15.0, y: 15.0) print("square. Origin is now at ((square. Origin. X), (square. 10.0)"Copy the code
This example defines three structures to describe the geometry:
Point
Encapsulate a(x, y)
The coordinates of theSize
Encapsulate awidth
And aheight
Rect
Represents a rectangle with an origin and dimensions
Rect also provides a computational property called Center. The center Point of a rectangle can be calculated from the origin and size, so there is no need to save it as an explicitly declared Point. Rect’s computed property Center provides custom getters and setters to get and set the center point of the rectangle as if it had a storage property.
In the example above, we create an instance of Rect called Square with an initial origin of (0, 0) and a width and height of 10. The blue square is shown in the figure below.
The center property of square can be accessed through the dot operator (square.center), which calls the getter for the property to get its value. Instead of simply returning an existing value, the getter actually represents the center Point of the square by calculating and returning a new Point. As the code shows, it returns the center point (5, 5) correctly.
The Center property is then set to a new value (15, 15) to move the upper-right square to the position shown in the orange square in the figure below. Setting the value of the property Center calls its setter to change the x and y values of the property Origin, thus moving the square to the new position.
Convenient setter declaration
If the setter for the evaluated property does not define a parameter name that represents the newValue, the default name newValue can be used. Here is the Rect structure code with the handy setter declaration:
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)
}
}
}
Copy the code
Read-only computing attribute
A computed property that has a getter and no setter is a read-only computed property. Read-only computed properties always return a value that can be accessed through the dot operator, but no new value can be set.
Note that you must use the var keyword to define computed attributes, including read-only computed attributes, because their values are not fixed. The let keyword is used only to declare constant properties that cannot be modified after initialization.
Read-only computed attributes can be declared without the get keyword and curly braces:
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: 5.0) 2.0) print(" The volume of fourByFiveByTwo is (fourbyfiveBytwo-.volume)") // Output "The volume of fourByFiveByTwo is 40.0"Copy the code
This example defines a structure called Cuboid, which represents a cube in three-dimensional space and contains the width, height, and depth properties. The structure also has a read-only computation property called volume that returns the volume of the cube. Providing a setter for the volume makes no sense, since there is no way to determine how to change the width, height, and depth values to match the new volume. However, it is useful for Cuboid to provide a read-only computation property that allows external users to get the volume directly.
Attribute viewer
Property viewer: Monitors and responds to changes in property values. The property viewer is called every time a property is set, even if the new value is the same as the current value.
You can add property observers for storage properties other than deferred storage properties, or you can add property observers for inherited properties, including storage properties and computed properties, by overwriting properties. You don’t need to add a property observer for non-overridden computed properties, because its setters can directly monitor and respond to changes in values.
You can add one or all of the following observers to the property:
willSet
Called before the new value is setdidSet
Called immediately after the new value is set
The willSet viewer passes in the new property value as a constant parameter. You can specify a name for this parameter in the willSet implementation code. If not, the parameter is still available, using the default name newValue.
Again, the didSet viewer will pass in the old property value as a parameter, which you can either name or use the default parameter name oldValue. If the property is assigned again in the didSet method, the new value overrides the old value.
Note that when a superclass property is assigned in the constructor of a subclass, its willSet and didSet observers in the superclass are called, and then the subclass’s observers are called. The observer is not called when a subclass assigns a value to the property before the parent class initializes the method call.
Here is an example of willSet and didSet in action, where a class called StepCounter is defined to count the total number of steps a person takes on a walk. This class can be used in conjunction with input data from a pedometer or other statistical device for daily exercise.
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 stepsCopy the code
The StepCounter class defines an Int property totalSteps, which is a storage property containing willSet and didSet observers.
When totalSteps is set to a new value, both its willSet and didSet observers are called, even if the new value is exactly the same as the current value.
The willSet observer in the example customizes the parameter representing the new value to newTotalSteps, and the observer simply outputs the new value.
The didSet viewer is called when the value of totalSteps has changed. It compares the new value to the old value, and if the total number of steps has increased, prints a message indicating how many steps have been increased. DidSet does not provide a custom name for the oldValue, so the default value oldValue represents the parameter name of the oldValue.
Pay attention to
WillSet and didSet are also called if the property is passed in-out to the function. This is because in-out arguments are in copy-in-copy mode: the argument’s copy is used inside the function, and after the function finishes, the argument is reassigned.
Global variables and local variables
The functions described by compute properties and the property viewer can also be used for global and local variables. A global variable is a variable defined outside of a function, method, closure, or any type. Local variables are variables defined inside a function, method, or closure.
The global or local variables mentioned in the previous section are storage variables, which, like storage properties, provide storage space for a particular type of value and allow reading and writing.
In addition, computational variables and observers for stored variables can be defined at either the global or local scope. A computed variable returns a computed result rather than a stored value and is declared in exactly the same way as a computed property.
Note that global constants or variables are calculated latedly, similar to lazy storage properties, except that global constants or variables do not require the lazy modifier. Locally scoped constants or variables are never delayed.
The type attribute
An instance attribute is an instance of a specific type. Each instance has its own set of attribute values, and the attributes of each instance are independent of each other.
You can also define attributes for the type itself, which are unique no matter how many instances of the type are created. These properties are called type properties. Type attributes are used to define data that is shared by all instances of a type, such as a constant that all instances can use (like static constants in C) or a variable that all instances can access (like static variables in C). Attributes of a stored type can be variables or constants. Attributes of a computed type can only be defined as variable attributes, as can attributes of an instance computed type.
Note that, unlike the instance’s stored type attributes, you must specify a default value for the stored type attributes, because the type itself does not have a constructor and therefore cannot be assigned to the type attributes using a constructor during initialization. Stored type properties are latently initialized; they are initialized only the first time they are accessed. Even if they are accessed by multiple threads at the same time, they are guaranteed to be initialized only once and do not require the lazy modifier.
Type attribute syntax
In C or Objective-C, static constants and static variables associated with a type are treated as global (
global
) defined by static variables. But in Swift, the type attribute is written inside the outermost curly braces of the type as part of the type definition, so its scope is within the scope of the type support.
Use the keyword static to define type attributes. When you define a computed type attribute for a class, you can use the keyword class to enable a subclass to override the implementation of the parent class. The following example demonstrates the syntax for stored and computed type attributes:
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 } }Copy the code
Note that the computed type attributes in the example are read-only, but you can define both read and write computed type attributes as well, using the same syntax as computed instance attributes.
Gets and sets the value of a type attribute
Like instance properties, type properties are accessed through the dot operator. However, type attributes are accessed through the type itself, not through the instance. Such as:
Print (SomeStructure. StoredTypeProperty) / / output "Some value. The" SomeStructure. StoredTypeProperty = "Another value." Print (SomeStructure. StoredTypeProperty) / / output "Another value. The" print putedTypeProperty (SomeEnumeration.com) / / output "6" PutedTypeProperty print (SomeClass.com) / / output "27"Copy the code
The following example defines a structure that uses two storage type properties to represent the volume of two channels, each with an integer volume between 0 and 10.
The figure below shows how two channels can be combined to simulate stereo volume. When the volume of the channel is 0, none of the lights will turn on; When the volume of the channel is 10, all the lights turn on. In this figure, the volume of the left channel is 9, and the volume of the right channel is 7:
The channel model described above is represented by an instance of the AudioChannel structure:
struct AudioChannel { static let thresholdLevel = 10 static var maxInputLevelForAllChannels = 0 var currentLevel: Int = 0 {didSet {if currentLevel > AudioChannel. ThresholdLevel {/ / limit current volume within the threshold currentLevel = AudioChannel. ThresholdLevel} if currentLevel > AudioChannel. MaxInputLevelForAllChannels {/ / store the current volume as a new maximum volume AudioChannel.maxInputLevelForAllChannels = currentLevel } } } }Copy the code
The structure AudioChannel defines two storage type properties to implement this function. The first is thresholdLevel, which represents the maximum upper threshold for volume. It is a constant with a value of 10, visible to all instances, and if the volume is higher than 10, the maximum upper limit of 10 is taken (described below).
The second type is a variable storage type attribute maxInputLevelForAllChannels, it used to represent all AudioChannel instance the maximum volume, the initial value is 0.
AudioChannel also defines a stored instance property called currentLevel, which represents the current volume of the current channel, with values ranging from 0 to 10.
The currentLevel property contains the didSet property observer to check the value of the property after each setting. It does two checks:
- if
currentLevel
The new value for is greater than the allowable thresholdthresholdLevel
, the property viewer willcurrentLevel
The value of is limited to the thresholdthresholdLevel
. - If the corrected
currentLevel
Value greater than the static type attributemaxInputLevelForAllChannels
The property viewer saves the new value inmaxInputLevelForAllChannels
In the.
Note that during the first check, the didSet property viewer sets currentLevel to a different value, but this does not cause the property viewer to be called again.
You can use the structure AudioChannel to create two channels, leftChannel and rightChannel, to represent the volume of a stereo system:
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
Copy the code
If the left channel currentLevel set to 7, type attribute maxInputLevelForAllChannels will update into 7:
LeftChannel. CurrentLevel = 7 print (leftChannel currentLevel) / / output "7" print (AudioChannel. MaxInputLevelForAllChannels) / / output "7"Copy the code
If you try to set the right channel currentLevel into 11, it will be fixed to a maximum of 10, at the same time, the value of maxInputLevelForAllChannels will update to 10:
RightChannel. CurrentLevel = 11 print (rightChannel. CurrentLevel) / / output "10" Print (AudioChannel. MaxInputLevelForAllChannels) / / output "10"Copy the code
Click on the:For more information
included