注:本文为自己学习The Swift Programming Language的笔记,其中的例子为引用原书和其他博文或自己原创的。每个例子都会批注一些实践过程中的经验或思考总结。
1.基础
Swift的属性描述特定的类、结构体和枚举的值。
属性分为存储属性和计算属性:存储属性以一个实例的形式储存变量或常量的值;计算属性可以计算相关属性的值而不是存储它。类、结构体和枚举可以有计算属性,但是只有类和结构体有存储属性。
除此之外,可以定义属性观察器来监视属性值的变化,然后作出相应响应。属性观察器能观察自己定义的存储属性,也可以观察继承自超类[superclass]的属性。
2.存储属性
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
我们看见在实例化时调用默认的构造函数,由于两个存储属性都没有默认值,只能调用注释存储属性名的默认构造函数并传入初始值。常量结构体由于结构体是值类型,它的所有属性都不能被改变,即使是变量存储变量。但作为引用类型的类就不同了,如果把引用类型实例赋给一个常量类,它的变量属性可以被改变。
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
rangeOfFourItems.firstValue = 1
这个例子中第二句会引发编译错误,常量结构体的变量存储属性也不能被修改。而类不同:
let video = VideoMode()
video.interlaced = true
第二句是没有错误的,因为类是引用类型。2.2懒惰存储属性
Swift的懒惰存储属性[Lazy Stored Property]指的是直到它第一次被使用时初始值才计算。声明一个存储属性是懒惰存储属性在它之前添加@lazy关键字。
懒惰属性只能是变量,因为它的初始值可以在类的实例化完成之后声明,但是常量属性的初始值必须在实例化完成之前声明。
懒惰属性有两个用途:
(1)属性的初始值由在实例化完成之后才能确立的外界因素决定。
(2)属性的初始值需要复杂或者开销很大的计算量来实现,因此这种属性的初始值在需要时才被计算。
引用原书中不完整的例子来给出懒惰属性的用法,这里是为了避免复杂的一个类初始化过程中没有必要的部分:
class DataImporter {
var filename = "data.txt"
/*
provide data importing functionality here
*/
}
class DataManager {
@lazy var importer = DataImporter()
var data = String[]()
/*
provide data management functionality here
*/
}
let manager = DataManager()
manager.data += "Some data"
这里定义了两个类,类DataImporter用来从外部文件获取输入数据,它有一个存储属性filename,假设这个类需要大量时间初始化(需要把文件内容读进内存)。类DataManager有一个懒惰属性importer,类型是DataImporter类,还有一个初始化为空字符串数组的存储属性data。类DataManager主要用于管理数据和提供对data这个数组的访问。
由于DataManager在不从外部文件读取数据的情况下也能进行其他工作,所以没有必要在初始化它的实例时就创建DataImporter的实例。
懒惰属性标识符写在let或var声明之前,直到使用它时才被初始化,例如:
println(manager.importer.fileName)
这时访问DataImporter的属性,就必须创建一个实例了。
3.计算属性
3.1取值器和赋值器
Swift类、结构体和枚举都支持另一种属性:计算属性,计算属性并不存储值,它提供可选的取值器[getter]和赋值器[setter]来间接的获取或这修改其他属性的值,通常计算属性和某个存储属性相关。
如下例的Square正方形类,一旦边长确定了,原点和中心点就建立了相关性。把原点作为存储属性,那么中心点就可以作为计算属性。能通过中心点的取值器和赋值器修改原点的值:
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)
}
}
}
取值器用get声明,他有一个类型是Point的返回值;赋值器有一个Point类型的穿入参数newCenter,这里可以使用默认传入参数名newValue而省略显式命名:
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
构建一个Rect实例来说明计算属性取值器赋值器使用过程:
var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
println("Initial square.center is at (\(initialSquareCenter.x), \(initialSquareCenter.y))")
// prints "Initial square.center is at (5.0, 5.0)"
square.center = Point(x: 15.0, y: 15.0)
println("Square.origin is now at (\(square.origin.x), \(square.origin.y))")
// prints "Square.origin is now at (10.0, 10.0)"
square.center在赋值给initialSquareCenter时调用center的取值器,从origin的值推出center的值,返回;在接受Point实例赋值时,调用center的赋值器,修改origin的值。
3.2只读计算属性
只读计算属性顾名思义外界只能获取他的值,它主要用在多个存储属性对一个计算属性构成的映射关系,比如长方形的长宽对应它的面积。只读计算属性定义可以用一般形式(只是不声明setter),也可以采用如下的省略形式(省略get关键字和它的一对大括号),比如在Rect类中添加一个只读计算属性area:
var area : Double {
return size.width * size.height
}
4.属性观察器属性观察器可以观察属性值是否[发生改变],并在马上发生改变之前[willSet]或者刚刚改变之后[didSet]做出一定的响应。[发生改变]指的是调用属性的赋值器(即使没有显式的赋值器),赋一模一样的值也会被其观察并响应。
除了懒惰属性以外的其他存储属性可以添加属性观察器,通过在子类中重写的方式继承存储或计算属性也可以添加属性观察器。而非继承的计算属性没有必要使用属性观察器,因为可以在它的构造器setter中实现。
willSet关键字定义发生马上改变之前的响应所调用的观察器,而didSet关键字定义刚刚发生改变之后的响应所调用的观察器。willSet可以显式定义一个参数名来指代立马要赋值给属性的值,隐式默认参数名为newValue;didSet可以显式定义一个参数名来指代刚刚赋值之前属性的原有值,隐式默认参数名为oldValue。
注意:属性第一次初始化的时候不会调用属性观察器,它们只在非初始化代码块以外的时候被调用。
属性观察器常用来输出一些调试信息,例如下例:
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
println("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
println("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
//prints About to set totalSteps to 200
//prints Added 200 steps
stepCounter.totalSteps = 500
//prints About to set totalSteps to 500
//prints Added 300 steps
可以看到willSet显式声明新值参数名为newTotalSteps,而didSet就用了旧值的默认参数名oldValue。
5.全局变量和局部变量
全局变量[global variable]指的是在函数、方法、闭包或者类型上下文外部定义的变量,而局部变量[local variable]指的是在函数、方法、闭包或者类型上下文内部定义的变量。上面所讨论的计算/存储属性、属性观察器等同样适用余全局变量和局部变量。
全局变量和局部变量可以是存储值并可以修改、取得的存储变量[stored variable],也可以是有构造器、赋值器但不存储值的计算变量[computed variable]。
注意:全局常量和全局变量是隐式延迟计算的,和懒惰属性特性一样,它不需要显式标注@lazy关键字。而局部常量和局部变量总是隐式非懒惰的。
5.类型属性
实例属性[instance property]指的是属于特定类型的实例的属性,每创建一个该类型的实例,它都有属于自己的一系列属性值,和其他实例的属性相互独立。而类型属性[type property]指的是属于这个类型的属性,无论创建多少个该类型的实例,它们都共享一份类型属性的备份。
对于值类型[value type](结构体和枚举),可以定义存储或计算的类型属性;而对于引用类型[reference type](类)只能定义计算的类型属性。
存储类型属性可以是变量也可以是常量,而计算类型属性一定是变量属性(计算实例属性也一样)。和存储实例属性不同的是,存储类型属性必须有一个默认值,因为一个类型它自己没有可以给存储类型属性赋初始值的构造函数。
5.1类型属性语法
Swift的类型属性的定义必须在类型定义的body中进行,值类型(结构体和类)的类型属性用static关键字前缀定义,而引用类型(类)的类型属性用class关键字前缀定义。
struct SomeStructure {
static var storedTypeProperty = "I am a structure."
static var computedTypeProperty: Int {
return 100
}
}
class SomeClass {
class var computedTypeProperty: Int {
<span> </span>return 100
}
}
类不能定义存储类型属性。5.2查询和设定类型属性
访问和设置类型属性也同样使用点运算符,只是点运算符作用对象是类型名,而不是实例名:
println(SomeStructure.storedTypeProperty)
SomeStructure.storedTypeProperty = "I am still a structure."
println(SomeStructure.storedTypeProperty)
/*
let someStructure = SomeStructure()
println(someStructure.storedTypeProperty)
*/
不仅是“不是实例名”,而是实例根本没有这个属性,去掉注释符号将引发一个编译错误。
在类型定义的body中也可以访问和修改类型属性,如下例:
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
}
}
}
}
thresholdLevel[常量存储类型属性]存储Audio的level上限,所有实例的[变量存储实例属性]currentLevel不能超越其值,而[变量存储类型属性]maxInputLevelForAllChannels则记录所有实例中level的最高者。