Attributes (properties)
- Storage attributes (Stored properties)
- Computed attributes (Computed properties)
- Attribute Observer (property observers)
- Global and local variables (Variables)
- Type property (Type properties)
Property associates a value with a specific class, struct, or enumeration. A store property stores a constant or variable as part of an instance, while a computed property evaluates (not stores) a value. Computed properties can be used for classes, structs, and enumerations, and stored properties are used only for classes and struct bodies.
Storage properties and computed properties are typically associated with instances of a particular type. However, attributes can also be applied directly to the type itself, which is called a type property.
In addition, you can define a property watcher to monitor changes in property values to trigger a custom action. Property observers can be added to their own defined storage properties, or to properties inherited from the parent class.
1. Storage Properties
Simply put, a stored property is a constant or variable stored in an instance of a particular class or struct. A stored property can be a variable store property (defined by the keyword Var) or a constant store property (defined by a keyword let).
You can specify a default value when defining storage properties, refer to the default attribute values (page 0) section. You can also set or modify the value of a stored property during construction, or even modify the value of a constant store property, refer to modifying a constant store property during the initialization phase.
The following example defines a struct named Fixedlengthrange, which describes an interval in which the range width cannot be modified after creation:
struct FixedLengthRange {var firstValue: Intlet length: Int}var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)// 该区间表示整数0,1,2rangeOfThreeItems.firstValue = 6// 该区间现在表示整数6,7,8
An instance of Fixedlengthrange contains a variable store property named Firstvalue and a constant storage property named length. In the example above, length is initialized when the instance is created, because it is a constant storage property, so its value cannot be modified after that.
1.1 Storage properties of a constant struct
If you create an instance of a struct and assign it to a constant, you cannot modify any of the properties of the instance, even if the variable store property is defined:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)// 该区间表示整数0,1,2,3rangeOfFourItems.firstValue = 6// 尽管 firstValue 是个变量属性,这里还是会报错
Because Rangeoffouritems is declared as a constant (with the Let keyword), even if Firstvalue is a variable property, it can no longer be modified.
This behavior is because the struct (struct) belongs to a value type. When an instance of a value type is declared as a constant, all its properties are also constants.
Classes that belong to reference types are not the same. After assigning an instance of a reference type to a constant, you can still modify the instance's variable properties.
1.2 Deferred storage properties
A deferred store property is a property whose initial value is evaluated when it is first called. Use lazy to mark a deferred storage property before the property declaration.
Attention:
You must declare the deferred storage property as a variable (using the var keyword), because the initial value of the property may not be available until the instance is constructed. The constant property must have an initial value before the construction process is complete, and therefore cannot be declared as a deferred attribute.
Deferred properties are useful when the value of a property relies on external factors that are not known until the end of the instance's construction process, or when the initial value of the property requires complex or large computations, it can be computed only when needed.
The following example uses deferred storage properties to avoid unnecessary initialization in a complex class. The Dataimporter and DataManager two classes are defined in the example, and the following is part of the code:
class DataImporter {/*DataImporter 是一个将外部文件中的数据导入的类。这个类的初始化会消耗不少时间。*/var fileName = "data.txt"// 这是提供数据导入功能}class DataManager {lazy var importer = DataImporter()var data = [String]()// 这是提供数据管理功能}let manager = DataManager()manager.data.append("Some data")manager.data.append("Some more data")// DataImporter 实例的 importer 属性还没有被创建
The DataManager class contains a storage property named data, and the initial value is an empty string array. Although not all of the code is written, the purpose of the DataManager class is to manage and provide access to the array of strings.
One function of DataManager is to import data from a file. This feature is provided by the Dataimporter class, and it takes time for the Dataimporter to complete initialization because its instance may open the file when it is initialized and read the contents of the file to memory.
DataManager may also complete the ability to manage data without importing data from the file. So when an instance of DataManager is created, it is not necessary to create an instance of Dataimporter, and it is wiser to create it when the first time it is used dataimporter.
Because lazy is used, the Importer property is created only when it is first accessed. For example, when accessing its properties FileName:
print(manager.importer.fileName)// DataImporter 实例的 importer 属性现在被创建了// 输出 "data.txt”
Attention:
If a property that is marked as lazy is accessed by multiple threads at the same time without initialization, there is no guarantee that the property will be initialized only once.
1.3 Storing properties and instance variables
If you have had objective-c experience, you should know that OBJECTIVE-C provides two ways to store values and references for class instances. For properties, you can also use instance variables as back-end storage for property values.
These theories are implemented uniformly using attributes in the Swift programming language. The properties in Swift do not have corresponding instance variables, and the backend storage of the properties is not directly accessible. This avoids the problem of access patterns in different scenarios, and simplifies the definition of attributes to a single statement. All of the properties in a type
Information-including naming, type, and memory management features-is defined in only one place (type definition).
2. Calculating properties
In addition to storing properties, classes, structs, and enumerations can define computed properties. The computed property does not store the value directly, but instead provides a getter and an optional
Setter to indirectly get and set the value of another property or variable.
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.centersquare.center = Point(x: 15.0, y: 15.0)print("square.origin is now at (\(square.origin.x), \(square.origin.y))")// 输出 "square.origin is now at (10.0, 10.0)”
This example defines 3 structures to describe a geometry:
1 point encapsulates the coordinates of a (x, y)
The Size encapsulates a width and a height
Rect represents a rectangle with an origin and size
Rect also provides a computed property called center. The center point of a rectangle can be calculated from the origin (origin) and size (size), so it is not necessary to save it as an explicitly declared point. The computed properties of Rect Center provides a custom getter and setter to get and set the center point of the rectangle as if it had a stored property.
In the example above, a Rect instance named Square is created with an initial value of (0, 0) and a width height of 10.
2.1 Setter Declaration
If the setter for the computed property does not define a parameter name that represents the new value, you can use the default name NewValue. The following is a Rect struct code that uses 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)}}}
2.2 Read-only computed properties
Only a getter without a setter is a computed property that is a read-only computed property. A read-only computed property always returns a value that can be accessed through the point operator, but cannot set a new value.
注意:
You must use the var keyword to define calculated properties, including read-only computed properties, because their values are not fixed. The Let keyword is used only to declare a constant property, indicating a value that cannot be modified after initialization.
The declaration of a read-only computed property can be stripped of the Get keyword and braces:
struct Cuboid {var width = 0.0, height = 0.0, depth = 0.0var 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)")// 输出 "the volume of fourByFiveByTwo is 40.0"
This example defines a struct named Cuboid, which represents a cube of three-dimensional space, containing the width, height, and depth properties. The struct also has a read-only computed property named volume to return the volume of the cube. Setting the value of the volume is meaningless because it is not possible to determine which of the values in width, height, and depth are modified to match the new volume, resulting in ambiguity. However, it is useful for Cuboid to provide a read-only computed property to allow external users to get the volume directly.
3. Property Watcher
Property observers monitor and respond to changes in property values, and each time a property is set to a value, the property observer is called, 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 overloading the properties. For property overloading, refer to overloading.
Attention:
You do not need to add a property observer for a computed property that is not overloaded, because you can monitor and respond to changes in values directly through its setter.
You can add one or all of the following observers for a property:
Willset called before the new value is set
Didset called immediately after the new value has been set
The Willset Observer will pass in the new property value as a constant parameter, you can specify a name for this parameter in the Willset implementation code, or the parameter is still available if not specified, and the default name NewValue is used.
Similarly, the Didset Observer will pass in the old property value as a parameter, either by naming it or by using the default parameter name OldValue.
Attention:
The Willset and Didset observers in the parent class are called when the properties of the parent class are assigned in the constructor of the child class.
For more information about the constructor agent, refer to constructor proxy rules for value types and constructors for classes.
Here is a practical example of Willset and Didset, which defines a class called Stepcounter, which is used to count the total number of steps when a person is walking. This class can be used in conjunction with the input data of a pedometer or other routine exercise statistics unit.
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 stepsstepCounter.totalSteps = 360// About to set totalSteps to 360// Added 160 stepsstepCounter.totalSteps = 896// About to set totalSteps to 896// Added 536 steps
The Stepcounter class defines an attribute totalsteps of type Int, which is a storage property that contains Willset and Didset observers.
When Totalsteps sets a new value, its willset and didset observers are called, even when 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, which simply outputs the new value.
The Didset Observer is called after the value of totalsteps is changed, comparing the new value to the old value, and if the total number of steps increases, the output of a message indicates how many steps are added. Didset does not provide a custom name for the old value, so the default value OldValue represents the parameter name of the old value.
Attention:
If you assign a value to a property in the Didset Observer, this value replaces the value set by the Observer.
4. Global variables and local variables
The schemas described by the computed properties and property observers can also be used for global variables and local variables. A global variable is a variable that is defined outside of a function, method, closure, or any type. A local variable is a variable defined inside a function, method, or closure.
The global or local variables mentioned in the previous section are stored variables, similar to stored properties, which provide a specific type of storage space and allow read and write.
In addition, you can define computed variables and define observers for stored-type variables at the global or local scope. Computed variables, like computed properties, return a computed value instead of a stored value, and the declaration format is exactly the same.
Attention:
Global constants or variables are deferred, similar to deferred storage properties, except that global constants or variables do not need to tag the lazy attribute. Local-scope constants or variables do not delay calculations.
5. Type properties
The properties of an instance belong to a particular type instance, each of which has its own set of property values each time the type is instantiated, and the attributes between the instances are independent of each other.
You can also define properties for the type itself, which have only one copy, regardless of the number of instances of the type. This property is the type attribute.
Type properties are used to define data shared by all instances of a particular type, such as a constant that all instances can use (just like a static constant in C), or a variable that all instances can access (just like a static variable in the C language).
A stored Type property of a value type can be a variable or constant, and a computed type property is defined as a variable attribute only as a computed property of an instance.
Attention:
Unlike the storage properties of an instance, you must specify a default value for the stored Type property, because the type itself cannot use the constructor to assign a value to the Type property during initialization.
5.1 Type attribute syntax
In C or objective-c, static constants and static variables associated with a type are defined as global static variables. However, in the Swift programming language, the type attribute is written as part of the type definition in curly braces at the outermost of the type, so its scope is within the range supported by the type.
Use the keyword static to define the Type property. When you define a computed type property for a class (class), you can use the keyword class to support subclasses overriding the implementation of the parent class. The following example shows the syntax for a stored-type and computed-type property:
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}}
Attention:
The computed Type property in the example is read-only, but you can also define a readable and writable computed type property, similar to the syntax for an instance-computed property.
2.5 Getting and setting the value of a Type property
As with the properties of an instance, access to the Type property is done through the dot operator. However, the Type property is obtained and set through the type itself, rather than through the instance. Like what:
print(SomeStructure.storedTypeProperty)// 输出 "Some value."SomeStructure.storedTypeProperty = "Another value."print(SomeStructure.storedTypeProperty)// 输出 "Another value.”print(SomeEnumeration.computedTypeProperty)// 输出 "6"print(SomeClass.computedTypeProperty)// 输出 "27"
Swift learns the fourth shot