Introduction
Property values are associated with a specific class, structure, or enumeration. As part of an instance, stored properties hold constant and variable values, whereas computed properties calculate (rather than store) a value. Classes, structures, and enumerations all provide computed properties. Only classes and structures give stored properties.
In most cases, stored and calculated properties are linked to instances of the same type. Properties, on the other hand, can be linked to the type itself. Type properties are a type of property.
You can also create property observers to track changes in a property's value, which you can react to using custom actions. Property observers can be applied to both your own stored properties and those that a subclass inherits from its superclass.
A property wrapper may also be used to reuse code in the getter and setter of numerous properties.
Type of Properties
Stored properties and computed properties are the two types of properties.
Stored Properties
In its most basic form, a stored property is a constant or variable that is saved as part of an instance of a class or structure. Two forms of stored properties are variable stored properties (introduced by the var keyword) and constant stored properties (introduced by the constant keyword) (introduced by the let keyword).
As stated in Default Property Values, you may define a default value for a stored property as part of its definition. During startup, you may also set and change the initial value for a stored property. As stated in Assigning Constant Properties During Initialization, this is true even for constant stored properties.
The following example constructs the FixedLengthRange structure, which specifies a range of integers whose length cannot be modified once it is created:
Program
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
print(rangeOfThreeItems)
// Output: FixedLengthRange(firstValue: 6, length: 3)
FixedLengthRange instances contain a changeable firstValue stored property and a constant length stored property.Because length is a constant property, it is initialised when the new range is established and cannot be altered after that.
Stored Properties of Constant Structure Instances
You can't change the properties of a structure instance that you construct and assign to a const, even if the properties were specified as variable properties:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
rangeOfFourItems.firstValue = 6
Output
Even though firstValue is a variable property, it is not feasible to alter rangeOfFourItems since it is specified as a constant (using the let keyword).
This is related to the fact that structures are value types. When a value type instance is marked as a constant, all of its attributes are also marked as constants.
The same cannot be said for reference types like classes. You may still alter the variable characteristics of a reference type instance if you assign it to a constant.
Lazy Stored Properties
A lazily stored property has an initial value that isn't calculated until it's utilised for the first time. The lazy modifier is written before the declaration of a lazily stored property.
Note: Because the initial value of a lazy property may not be obtained until after instance initialization is complete, you must always declare it as a variable (with the var keyword).Constant properties cannot be designated lazy because they must always have a value before initialization is complete.
When the initial value of a property is reliant on outside factors whose values aren't known until after an instance's setup is complete, lazy properties are beneficial. When the initial value for a property involves a sophisticated or computationally costly setup that shouldn't be done until and until it's needed, lazy properties come in handy.
To minimise wasteful initialization of a complicated class, the example below employs a lazy stored property. DataImporter and DataManager are two classes defined in this example, neither of which is displayed in their entirety:
Program
class DataImporter {
/*
DataImporter is a class that allows you to import data from a file. It is believed that initialising the class would take some time.
*/
var filename = "data.txt"
// The DataImporter class would be used to import data in this case.
}
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
print(manager.data)
// Output: ["Some data", "Some more data"]
The data stored property of the DataManager class is initialised with a fresh, empty array of String values. The objective of this DataManager class is to manage and offer access to this array of String data, even though the rest of its functionality isn't exposed.
The DataManager class has the ability to import data from a file as part of its capabilities.
The DataImporter class provides this capability, and it is believed that initialising it would take some time. This might be because a DataImporter instance must open a file and read its contents into memory when it is formed.
print(manager.importer.filename)
Note: If many threads access a property marked with the lazy modifier at the same time and the property hasn't been initialised yet, there's no assurance that the property will be initialised just once.
Stored Properties and Instance Variables
If you've worked with Objective-C before, you're probably aware that there are two methods to store values and references in a class instance.In addition to properties, instance variables may be used to back up the values contained in properties.
Swift brings these ideas together in a single property declaration. There is no instance variable for a Swift property, and the backing store for a property isn't accessible directly. This method reduces ambiguity about how the value is obtained in multiple situations and consolidates the declaration of the property into a single, decisive statement. As part of the type's specification, all information about the property is defined in one place, including its name, type, and memory management features.
Computed Properties
Classes, structures, and enumerations can define computed properties, which don't actually save a value, in addition to stored properties. Instead, they give an optional setter and a getter to indirectly get and set additional properties and values.
Program
// declaring the point
struct Point {
var x = 0.0, y = 0.0
}
// declaring the height and width
struct Size {
var width = 0.0, height = 0.0
}
// declaring the Reactangle
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)
}
}
}
// creating the object of Rectangle structure
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))")
Output
Shorthand Setter Declaration
If the setter for a calculated property doesn't specify a name for the new value to be set, newValue is used by default. Here's another version of the Rect structure that makes use of the shorthand notation:
Program
// declaring the point
struct Point {
var x = 0.0, y = 0.0
}
// declaring the height and width
struct Size {
var width = 0.0, height = 0.0
}
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)
}
}
}
var rect = AlternativeRect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 15.0))
let initialSquareCenter = rect.center
rect.center = Point(x: 15.0, y: 15.0)
print("Rect is now at (\(rect.origin.x), \(rect.origin.y))")
Output
Shorthand Getter Declaration
If a getter's whole body is a single expression, the getter returns that expression implicitly.
This shorthand notation, as well as the shorthand notation for setters, are used in another version of the Rect structure:
Program
// declaring the point
struct Point {
var x = 0.0, y = 0.0
}
// declaring the height and width
struct Size {
var width = 0.0, height = 0.0
}
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)
}
}
}
var rect = CompactRect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 15.0))
let initialSquareCenter = rect.center
rect.center = Point(x: 15.0, y: 15.0)
print("Rect is now at (\(rect.origin.x), \(rect.origin.y))")
Output
Read-Only Computed Properties
A read-only computed property is a computed property that has a getter but no setter. A read-only calculated property returns a value and can be accessed using the dot syntax, but it cannot be modified.
Note: Because their value isn't fixed, computed properties—including read-only computed properties—must be declared as variable properties with the var keyword. The let keyword is exclusively used for constant properties, indicating that their values cannot be modified once they've been set during instance initialization.
By eliminating the get keyword and associated brackets from the declaration of a read-only calculated property, you may make it simpler:
Program
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let _volume = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(_volume.volume)")
Output