属性
存储属性:存储常量或变量作为实例的一部分,计算属性计算(而不是存储)一个值。
计算属性:可以用于类、结构体和枚举里,存储属性只能用于类和结构体.
存储属性
简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量,存储属性可以是变量存储属性(用关键字var定义),也可以是常量存储属性(用关键字let定义)。
可以在定义存储属性的时候指定默认值。
struct FixedLengthRange { var firstValue: Int let length: Int //常量存储属性一旦赋值就无法修改 } var rangeOfThreeItems = FixedLengthRange(firstValue: 0,length: 3) //该区间表示整数0,1,2 rangeOfThreeItems.firstValue = 6 // 该区间现在表示整数6,7,8 //如果创建了一个结构体的实例并赋值给一个常量,则无法修改实例的任何属性,即使定义了变量存储属性 let rangeOfFourItems = FixedLengthRange(firstValue: 0,length: 4) // 该区间表示整数0,2,3 //rangeOfFourItems.firstValue = 6 // 尽管firstValue是变量属性,这里还是会报错 //这种行为是由于结构体(struct)属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。 //属于引用类型的类(class)则不一样,把一个引用类型的实例赋给一个常量后,仍然可以修改实例的变量属性。
延迟存储属性
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用lazy来标示一个延迟存储属性。
注意:必须将延迟存储属性声明成变量(使用var关键字),因为属性的值在实例构造完成之前可能无法得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
延迟属性很有用,当属性的值依赖于在实例的构造过程结束前无法知道具体值的外部因素时,或者当属性的值需要复杂或大量计算时,可以只在需要的时候来计算它。
下面的例子使用了延迟存储属性来避免复杂类的不必要的初始化。例子中定义了DataImporter和DataManager两个类,下面是部分代码:
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") // DataManager的 importer 属性还没有被创建 //由于使用了lazy,DataManager的importer属性只有在第一次被访问的时候才被创建。比如访问它的属性fileName时: println(manager.importer.fileName)
计算属性(getter方法运算后返回的属性)
除了存储属性,类、结构和枚举能够定义计算属性。计算属性并不存储值,它提供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) println("square.origin is now at (\(square.origin.x),\(square.origin.y))") // prints "square.origin is now at (10.0,10.0)"
分析:定义了一个名为square的Rect变量,它的中心点初始化为(0, 0),高度和宽度初始化为10,定义了一个initialSquareCenter常量,通过square.center的getter方法得到值(5,5),在通过setter方法将center属性设置值为(15,15) .改变origin中坐标x和y的值
setter声明的简略写法
如果计算属性的setter方法没有将被设置的值定义一个名称,将会默认地使用newValue这个名称来代替。下面的例子采用了这样一种特性,定义了Rect结构的新版本:
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 {//默认使用newValue origin.x = newValue.x - (size.width / 2) origin.y = newValue.y - (size.height / 2) } } }
只读计算属性
只读计算属性只带有一个getter方法,通过点操作符,可以放回属性值,但是不能修改它的值。
注意:应该使用var关键字将计算属性-包含只读计算属性-定义成变量属性,因为它们的值并不是固定的。let关键字只被常量属性所使用,以表明一旦被设置它们的值就是不可改变的了
通过移除get关键字和它的大括号,可以简化只读计算属性的定义:
struct Cuboid { var width = 0.0,height = 0.0,depth = 0.0 var volume: Double { //只定义getter方法 return width * height * depth } } let fourByFiveByTwo = Cuboid(width: 4.0,height: 5.0,depth: 2.0) println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)") // prints "the volume of fourByFiveByTwo is 40.0"
属性观察者 Property Observers
属性观察者观察属性值的改变并对此做出响应。当设置属性的值时,属性观察者就被调用,即使当新值同原值相同时也会被调用。
除了懒惰存储属性,你可以为任何存储属性加上属性观察者定义。另外,通过重写子类属性,也可以继承属性(存储或计算)加上属性观察者定义。属性重写在“重写”章节定义。
注意:不必为未重写的计算属性定义属性观察者,因为可以通过它的setter方法直接对值的改变做出响应
willSet:设置值前被调用
didSet:设置值后立刻被调用
当实现willSet观察者时,新的属性值作为常量参数被传递。你可以为这个参数起一个名字,如果不的话,这个参数就默认地被命名成newValue。
在实现didSet观察者时也是一样,只不过传递的产量参数表示的是旧的属性值。
注意:属性初始化时,willset和didSet并不会被调用。只有在初始化上下文之外,当设置属性值时才被调用
下面是一个willSet和didSet用法的实例。定义了一个类StepCounter,用来统计人走路时的步数。它可以从计步器或其它计数器上获取输入数据,对日常联系锻炼的步数进行追踪。
class StepCounter { var totalSteps: Int = 0 { //属性观察者 willSet(newTotalSteps) {//willSet设置前被调用 println("About to set totalSteps to \(newTotalSteps)") println("willSet totalSteps vaule is \(totalSteps)") } didSet {//didSet设置值后立刻被调用,默认使用oldValue表示旧值 if totalSteps > oldValue { println("Added \(totalSteps - oldValue) steps") println("didSet totalSteps vaule is \(totalSteps)") } } } } let stepCounter = StepCounter() stepCounter.totalSteps = 200 // About to set totalSteps to 200 // Added 200 steps stepCounter.totalSteps = 360 // About to set totalSteps to 360 // Added 160 steps stepCounter.totalSteps = 896 // About to set totalSteps to 896 // Added 536 steps