Swift属性
属性概述
在Swift中,属性是类、结构体甚至枚举的组成部分。
存储型属性和计算型属性
OC中属性有两种:「普通属性」和「合成属性」,譬如对于一个UIView,center和bounds是其普通属性(是由对应的存储单元与之对应),而frame是合成属性(根据center和bounds而成而来)。在Swift中也有类似概念,只不过被称为:「存储型属性」(stored properties)和「计算型属性」(computed properties)。
注意:「存储型属性」可以存在于类、结构体以及枚举中,但「计算型属性」只能被类或结构体所有。
实例属性和类型属性
「存储型属性」和「计算型属性」是根据「是否对应存储单元」而分的;
根据『所有者』又可以分为「实例属性」和「类型属性」(type properties)。
P.S:「类型属性」在其他语言中更多时候被称为「类属性」(或「静态变量」),但在Swift的世界里,结构体和枚举也可以有属性,所以叫「类型属性」更合适。
属性监控
在Swift中,针对属性有一个非常高级的功能:监听属性。简单来说,可以通过定义属性监视器来控制属性的变化,一次来触发一个自定义的操作;属性监视器可以添加到自己写的属性上,也可以添加到从父类继承的属性上。
存储型属性
简单来说,一个「存储型属性」就是特定类或结构体的实例的一个常量或变量,「存储型属性」可以是变量存储型属性(关键字var定义),也可以是常量存储型属性(用关键字let定义)。
存储型属性的定义
Swift中属性的定义非常简单,和普通变量/常量的定义差不多,如下:
class SomeClass{ var someClassProperty: String = "" } class SomeStructure { var someStructureProperty: String = "" } class SomeEnumeration { var someEnumerationProperty: String = "" }
存储型属性的初始化
和变量一样,在使用存储型属性(无论是内部方法使用还是外部使用)之前需要对其进行初始化,关于存储型属性初始化:
- 可以在定义存储型属性的时候指定默认值;
- 可以在构造过程时(initializer中)设置或修改存储型属性的值(无论是常量还是变量);
指定默认值
指定默认值的方式有很多种,包括:「字面值赋值」,「创建实例赋值」,「函数赋值」,「闭包赋值」等等。举个栗子:
class ZWView { // 直接初始化 var tag: Int = 0 // 字面值赋值 var bounds: CGSize = CGSize(width: 0.0, height: 0.0) // 创建实例赋值 // 使用「闭包」或「函数」赋值 var center: CGPoint = CGPointMake(0.0, 0.0) // 函数赋值 var description: String = { // 闭包赋值 return "I am a ZWView" }() }
在initializer中初始化
class ZWView { var superView: ZWView?
// 在initializer中初始化初始化存储型属性 init() { self.superView = nil }
}
值得一提的是,当为「存储型属性」设置默认值或者是在「构造器」中为其赋值时,它们的值都是被直接设置的,不会触发任何属性监测器(property observers)。
延迟存储型属性
在OC(其他语言也差不多),总会有些lazy处理的东东,譬如对于UITableViewCell,它有一个属性叫detailTextLabel
,它是一个UILabel对象,根据我的理解,UITableViewCell默认初始化情况下该对象是不会被初始化的(毕竟不是每个UITableViewCell都需要展示detail信息嘛),当第一次访问它时,会在detailTextLabel的getter中初始化它,一般会这儿写:
- (UILabel)detailTextLabel { if (_detailTextLabel == nil) { _detailTextLabel = [[UILabel alloc] init]; [self addSubView:_detailTextLabel]; } return _detailTextLabel; }
这是OC中一种典型的lazy处理方式。但在Swift中,lazy处理上升到了语法层面,Swift称之为:「延迟存储型属性」(lazy stored property)。它是指第一次被调用的时候才会计算其初始值的属性。在属性声明前使用lazy
来表示一个延迟存储型属性。
注意:必须将延迟存储型属性声明成变量(使用var关键字),因为属性的值在实例构造完成之前可能无法得到。而常量属性在initialization前可能无法得到。
延迟属性很有用,当属性的值依赖于在实例的initialization结束前无法知道具体值的外部因素时,或者当属性的值需要复杂或大量的计算时,可以只在需要的时候来计算它。
存储型属性和实例变量
在OC中,普通属性(非合成属性)一般都对应有一个实例变量,譬如名为name的属性一般有一个名为_name实例变量与之对应;在实际编程过程,在类的内部很多时候直接操作实例变量,在外部才操作属性(访问或赋值)。
但在Swift中,刻意模糊「存储型属性」和「实例变量」这两个概念,简而言之,对于名为name的属性,它没有别的实例变量名称,或说它的实例变量名称也叫name。
计算型属性
计算型属性的定义
在OC中,所有属性(无论是普通属性还是合成属性)都可以定义setter和getter,但Swift将逻辑区分得更加清晰。对于「存储型属性」,Swift提供了lazy
以及属性监视器(willSet
和didSet
),用来处理以前在OC中需要在getter或setter中额外的逻辑;也因为如此,Swift不再允许「存储型属性」拥有setter和getter(因为没必要嘛);setter和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) } } }
可以看到,在Swift中,getter和setter的形式分别是:get{}
和set(newPropertyName){}
。setter还可以写得更简洁一点(将形参去掉,使用关键字newValue),如下:
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而不定义setter就可以了。
值得一提的是,一定要使用var
关键字修饰「计算型属性」,文档是这么描述的:
You must declare computed properties — including read-only computed properties — as variable properties with the
var
keyword, because their value is not fixed. Thelet
keyword is only used for constant properties, to indicate that values cannot be changed once they are set as part of instance initialization.
属性监视器(Property Observers)
上文已经多次提到「属性监视器」。属性监视器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性监视器,甚至新的值和现在的值相同的时候也不例外。
P.S:我刚开始接触「属性监视器」这个概念时,还以为它可以提供类似于OC中KVO的功能呢,后来发现想多了,Swift中的lazy、属性监视器所提供的功能在OC中的getter和setter中都能实现,只不过Swift将这些逻辑区分得更加清晰而已。
Swift允许可以为延迟属性之外的其他存储型属性添加属性监视器,也可以通过重载的方式为继承的属性(包括存储型属性和计算属性)添加属性监视器。
注意哦,不能为non-overridden computed property添加属性监视器!文档是这么描述的:
You don’t need to define property observers for non-overridden computed properties, because you can observe and respond to changes to their value from directly within the computed property’s setter.
所谓的属性监视器说白了就是类似于getter和setter的代码块,只不过是由关键字willSet
和didSet
引导的。willSet
和didSet
都携带一个参数,它们的默认名分别为newValue
和oldValue
,用来表示新值
和旧值
,用户可以自己定义一个新的名字。所以willSet和didSet的一般是这样的:
class Student: NSObject { var age: Int = 0 { willSet { println("新值=\(newValue)") } didSet { println("旧值=\(oldValue)") } } }
总之,willSet
和didSet
的功能并不是很强大,我想基于它们恐怕很难有牛逼哄哄的「奇技淫巧」吧!
类型属性
在其他语言中,类本身所定义的属性往往叫「类属性」(或静态变量),「类属性」不属于任何一个实例,不管该类类型有多少个实例,这些属性都只有一份。只不过在Swift的世界里,结构体和枚举也可以有属性,所以这种属性被称为「类型属性」(也是更合适的叫法)。
在Swift中,对于值类型(即结构体和枚举),可以定义存储型或计算型类型属性;但对于引用类型(类),只能定义计算型类型属性。
P.S:闹不明白为什么有这种设定,希望以后能有个比较好的解释吧。
P.P.S:在实际使用中,发现Swift(使用的是环境是Xcode 6.3,Swift 1.2)又允许类类型定义存储型类型属性,但最新《The Swift Programming Language》中又没有提到。挺郁闷的!无论如何,暂且认为Swift允许定义存储型类型属性吧!
和存储型实例属性不同,定义存储型类型属性时必须为它指定一个默认值,因为对于每个实例而言,可以在initializer中初始化属性,但是对一个类型而言,它是没有所谓的initializer的。
类型属性语法
Swift规定,对于值类型(即结构体、枚举)的类型属性,使用关键字static
来修饰;对于引用类型(即类类型)的类型属性,使用关键字class
来修饰,但也可以使用关键字static
来修饰;被class
修饰的类类型属性允许在子类中override该类型属性,但被static
修饰的类类型属性不允许被子类override,效果等同于final class
。
如下是一些使用例子:
struct SomeStructure { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here get { return 0 } } } enum SomeEnumeration { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here get { return 0 } } } class SomeClass { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here get { return 0 } } class var overrideableComputedTypeProperty: Int { // return an Int value here get { return 0 } } }
注意:例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟实例计算属性的语法类似。
P.S:个人感觉,类型属性的定义语法似乎还没确定下来,以后或许还有变数!
获取和设置类型属性的值
跟实例的属性一样,类型属性的访问也是通过点运算符来进行,但是,类型属性是通过类型本身来获取和设置,而不是通过实例。比如:
println(SomeClass.computedTypeProperty) // 输出 "0" println(SomeStructure.storedTypeProperty) // 输出 "Some value." SomeStructure.storedTypeProperty = "Another value." println(SomeStructure.storedTypeProperty) // 输出 "Another value.”
其实,关于「属性」还有很多内容,譬如「属性和继承」「属性和重载」「属性与作用域」等等,以后补充吧!