第十章 属性
3. Property Observes (属性观察器)
属性观察器监视属性值的变化和及时对属性值的变化作出响应。每次属性值被设定之后都会调用属性观察器。无论是新的属性值是否与原来的属性值相同。只要属性值改变。属性观察器都会对此作出响应。
我们可以将属性观察器添加到结构体和类的存储属性里面,不可以将属性观察器添加到存储属性的延迟存储属性里。可以在一个子类里面以重写的方式添加一个属性观察器到任意一个继承属性(存储或计算属性)。you can also add property observers to any inherited property ( whether stored property or computed ) by overriding the property within a subclass。
计算属性如果不能重写那么就不能在该计算属性里面添加属性观察器了,因为我们在计算属性setter里对属性值的变化进行监视和响应。属性重写会在Overriding
里面有详细说明。
willSet
- 存储新的属性值前会被调用
didSet
- 重新分配新的属性值后会被调用
实现调用 willSet和didSet对新的属性值变化进行监视和响应。
willSet观察器会将新的属性值作为常量参数传入,在willSet
的实现代码中可以为这个参数指定一个名称,如果不指定这个参数名和括号,默认的情况下就会使用newValue
同样didSet观察器会将旧的属性值作为常量参数传入,如果我们要给这个属性的didSet观察器分配值的时候,可以命名该参数也可以使用这个参数的旧名称oldValue。在didSet观察器中给新的属性分配一个值,那么设置的新值会覆盖原有旧的值。
class StepCounter {
// StepCounter的一个变量计算属性
var totalSteps: Int = 0 {
/* 把willSet和didSet的观察器添加到这个变量计算属性里面,
给willSet观察器设置的名称用于传数据给类的变量计算属性
新的变量值被添加前会调用 willSet观察器
*/
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
/* 默认情况下didSet观察器没有设置新的名称就用oldValue
新的变量被添加后调用willSet观察器
*/
didSet {
// 用条件语句来判断总步数和原步数
if totalSteps > oldValue {
// 从而来确定添加的步数(总步数 - 原步数 = 新添加的步数)
print("Add \(totalSteps - oldValue) steps")
}
}
}
}
// 实例化并引用类,
let stepCounter = StepCounter()
// 重新分配值将会被
stepCounter.totalSteps = 200
stepCounter.totalSteps = 360
stepCounter.totalSteps = 896
4. Global and Local Variables ( 全局和局部变量 )
计算属性和它的属性观察器所拥有的那些特征,特性,功能都可以被应用到全局变量和局部变量中。全局变量就是在函数,方法,闭包或其他类型之外定义的变量,局部变量是被定义在函数,方法,闭包或其他类型里面的变量。
在全局变量和局部变量都可以定义计算型变量和为存储型变量所定义的观察器。计算型变量是计算值而不是存储值,写法和计算属性(computer property)是一样的。
全局常量和全局变量都是进行延迟计算的和延迟存储属性比较相似。全局常量和全局变量不需要lazy
修饰符,这一点与延迟存储属性不太一样。局部常量和局部变量从来都不会延迟计算。
5. Type Properties (类型属性)
实例属性(Instance property
)是一个特定类型的实例,为该类型所创建的实例,这个实例都会拥有一套自己相互独立的属性值。可以为类型定义一个或多个属于它自己的属性,而不是为定义某个类型的实例,无论创建了多少个实例,这些属性都是独一无二的,这些属性就叫做类型属性。
类型属性是用来定义某个类型里面所有实例所有共享的值。存储类型属性(stored type property)可以是常量或变量,计算型类型属性和计算型实例属性都是一样只能被定义称变量
5.1 Type Property Syntax (类型属性语法)
类型属性在C和Objective-C语言里可以与某个类型关联的静态常量(static constant
)和变量作为全局静态变量定义的。而在swift语言里类型属性会作为该类型定义的一部分写在类型定义的大括号内,所以呢 类型属性的作用范围也就只能在该类型支持的范围内。
static
关键字引出介绍该类型的类型属性,它的作用范围就在它关联的类型所支持的范围内。计算型类型属性要用class
关键字来替代static来支持子类对父 类的实现进行重写。
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
}
}
5.2 Querying and Setting Type Property (查询和设置类型属性)
可以像查询和设置实例属性那样用dot语法来查询设置类型属性。然而,类型属性可以在类型本身里面设置和查询类型属性,而不是在该类型的实例里面。
print(SomeStructure.storedTypeProperty)
// 输出:"Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 输出:"Another value."
print(SomeEnumeration.computedTypeProperty)
// 输出:"6"
print(SomeClass.computedTypeProperty)
// 输出:"27"
下面的例子定义了一个结构体,使用两个存储型类型属性来表示两个声道的音量,每个声道具有 0 到 10 之 间的整数音量。下图展示了如何把两个声道结合来模拟立体声的音量。当声道的音量是 0 ,没有一个灯会亮;当声道的音量是 10 ,所有灯点亮。本图中,左声道的音量是 9 ,右声道的音量是 7 :
struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
// **
var currentLevel: Int = 0 {
// **
didSet {
if currentLevel > AudioChannel.thresholdLevel {
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// 输出:"7"
print(AudioChannel.maxInputLevelForAllChannels)
// 输出:"7"
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// 输出:"10"
print(AudioChannel.maxInputLevelForAllChannels)
// 输出:"10"