Properties
Opened this issue · 0 comments
属性将值和特定的类,结构体或枚举相关联。存储属性将常量和变量值存储为实例的一部分,而计算属性计算值而不是存储值。类,结构体及枚举都支持计算属性,但是只有类和结构体支持存储属性。
存储属性和计算属性通常都和特定的类型实例相关联。然而,属性也可以跟类型本身相关联,这样的属性称为类型属性。
除此之外,我们可以定义属性观察者来监听某个属性值的变更,这样我们可以相应的执行一些自定义操作。属性观察者既可以添加到我们自己定义的存储属性中,也可以添加到从父类继承而来属性中。
我们还可以使用属性包装器在多个属性的getter和setter中重用代码
存储属性
结构体常量实例的存储属性
如果我们创建了一个结构体实例,并且将这个实例赋值给一个常量,那么我们不可以修改这个实例的属性,即便这些属性被声明为常量属性:
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属性,即便这个属性是一个变量属性。
这种行为是由于结构体是值类型导致的。当一个值类型的实例被标记为常量时,这个实例的所有属性也是常量(不可更改)。
但这对于类不适用,因为类是引用类型。如果我们将引用类型的实例赋值给一个常量,但是我们仍然可以更改这个实例的变量属性。
懒加载存储属性
懒加载存储属性只有在第一次使用的时候才去计算它的初始值。在声明前面加上lazy关键字来表明这是一个懒加载存储属性。
⚠️ 我们必须始终将懒加载属性声明为变量(使用var关键字),因为它的初始值可能在初始化过程完成之后才被获取。常量属性必须始终在初始化完成之后就拥有数值,因此常量属性不能被声明为lazy.
当属性的初始值是由外部的因素决定的,并且只有在实例的初始化完成之后才可以知道它的值的时候,将其声明为懒加载属性非常有用。懒加载属性的初始值如果需要复杂或者耗时的计算,那这些计算只有在它使用的时候才会执行。
下面的代码使用了懒加载存储属性来让一个复杂的类避免不需要的初始化:
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数组。尽管这个类其余的功能并没有展示出来,但是它的作用是管理和提供String数组数据。
⚠️ 如果懒加载属性同时被多个线程获取,并且此时它还没有被初始化,那么并不会保证这个属性只会被初始化一次
存储属性和实例变量
我们知道作为类实例的一部分,Objective-C提供了两种方式来存储数值和引用。除了属性之外,我们可以使用实例变量来存储属性中的值。
Swift将这些概念统一成了属性声明。Swift属性并没有对应的实例变量,并且不能直接访问属性的backing store。这种方式避免了关于在不同上下文中如何获取值的困惑,以及将属性的声明简化成了单条明确的语句。关于属性的所有信息(名称,类型,及内存管理特性)都作为类型声明的一部分定义在了同一位置。
计算属性
除了存储属性之外,类/结构体/枚举还可以定义计算属性,计算属性实际上不会存储数值。相反,它们提供一个getter和可选的setter来间接获取其他属性或者数值或者对其进行设置
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)"
Setter声明的简写
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)
}
}
}
Getter声明的简写
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)
}
}
}
只读的计算属性
一个只有getter没有setter的计算属性叫做只读的计算属性。一个只读的计算属性总是会返回值,并且可以通过点语法获取到,但是不能设置新的数值。
属性观察器
属性观察器监听某个属性的数值,并且对其值的变更做出响应。每当属性的值被设置之后,属性观察器就会被调用,即便新设置的值和该属性的当前值一样。
我们可以向定义的任何存储属性添加属性观察器,除了懒加载存储属性。我们也可以通过在子类中对继承的任何属性(无论是存储属性还是计算属性)进行重写以添加属性观察器。我们没必要对非重写的计算属性定义属性观察器,因为我们可以在计算属性的setter中对其值进行观察及对其变更做出响应。
⚠️ 当父类的属性在子类的构造器中被设置的时候,父类属性的willSet和didSet观察器会在父类的构造器调用之后被调用。如果在父类构造器被调用之前,对自己的属性进行设置是不会触发属性观察器。