到目前为止,本章所介绍的变量都是存储下来的变量,就像盒子一样。变量是个名字,就像盒子一样;值可以通过赋给变量放到盒子中,然后等待通过引用该变量进行获取,只要变量存在就行。
此外,变量还可以计算出来。这意味着变量不再持有值,而是持有函数。在给变量赋值时,函数setter会被调用。当引用变量时,另一个函数getter会被调用。如下代码演示了声明计算变量的语法:
var now : String { ① get { ② return NSDate.description ③ } set { ④ print(newValue) ⑤ }}
①变量必须是个var(不能是let)。其类型必须要显式声明,后跟一对花括号。
②getter函数叫作get。注意到这里并没有正式的函数声明;单词get后跟一对花括号,里面是函数体。
③getter函数必须要返回与变量类型相同的值。
④setter函数叫作set。这里并没有正式的函数声明;单词set后跟一对花括号,里面是函数体。
⑤setter的行为就像是接收一个参数的函数。在默认情况下,参数通过局部名newValue进入setter函数中。
如下代码演示了计算变量的用法,这与其他变量没什么大的差别。该赋值时就赋值,该使用时就使用。不过在幕后,setter与getter函数会被调用:
now = /"Howdy/" // Howdy ①print(now) // 2015-06-26 17:03:30 +0000 ②
①为now赋值会调用其setter。传递给调用的参数就是所赋的值,这里就是/"Howdy/"。该值进入set函数后成为newValue。set函数会将newValue打印到控制台上。
②获取now会调用其getter。get函数会获取到当前的日期时间,然后将其转换为字符串并返回。接下来将该字符串打印到控制台上。
注意到第1行将now设为/"Howdy/"时,字符串/"Howdy/"并没有存储下来。比如,它对于第2行的now值就没起任何作用。set函数可以存储值,不过它无法将其存储到计算变量中;计算变量是不可存储的!它只是调用getter与setter函数的便捷方法而已。
上述语法有几个变种:
·set函数的参数名不一定非得叫作newValue。要想指定不同的名字,将其放到单词set后面的圆括号中即可,如下代码所示:
set (val) { // now you can use /"val/" inside the setter function body
·不一定要有setter。如果省略setter,那么变量就变成只读的了。为其赋值会导致编译器报错。没有setter的计算变量是Swift中创建只读变量的主要方式。
·一定要有getter!如果没有setter,那么单词get与后面的花括号就可以省略了。如下代码是声明只读变量的合法方式:
var now : String { return NSDate.description}
计算变量在很多地方都很有用。下面是其在实际使用中经常用到的地方:
只读变量
计算变量是创建只读变量最简单的方式,只需在声明中省略setter即可。通常情况下,变量是全局变量或属性;局部只读变量的意义并不大。
函数门面
如果每次需要一个值时,它都会由一个函数计算出来,那么可以通过更简单的语法将其表示为一个只读的计算变量。如下示例来自我所编写的代码:
var mp : MPMusicPlayerController { return MPMusicPlayerController.systemMusicPlayer}
每次想要引用时,都可以调用MPMusicPlayerController.systemMusicPlayer(),通过简单的名字mp来引用会更加紧凑一些。mp表示一个事物而非动作表现,因此将mp当作变量会更好一些,这样在所有出现mp的地方,它都表示一个事物而非返回事物的函数。
其他变量的门面
计算变量可以位于存储变量之前,作为一个守护者,来确定如何设置和获取那些存储变量。这是相比于Objective-C的访问器方法的。在极端情况下,公开的计算变量是由一个私有的存储变量所维护的:
private var _p : String = /"/"var p : String { get { return self._p } set { self._p = newValue }}
这个示例本身没什么意义,因为对于访问器没什么可做的:我们只是直接设置和获取私有的存储变量而已,因此p与_p之间并没有什么实际的差别。但是基于该模板,你可以添加一些功能,在设置和获取时完成一些额外的事情。
正如上面的示例所示,计算实例属性函数可以引用其他实例属性,还可以调用实例方法。这是很重要的,因为一般来说,存储属性的初始化器这两件事都做不了。计算属性之所以可以,是因为直到实例存在了才可以调用它的函数。
如下示例演示了如何将计算变量用作存储门面。类有一个实例属性,它存储的数据很大,并且可以为nil(它是一个Optional,稍后将会介绍):
var myBigDataReal : NSData! = nil
当应用进入后台时,我想减少内存使用(因为iOS会杀掉占据大量内存的后台应用)。因此,我计划将myBigDataReal数据保存为文件并存储到磁盘上,然后将变量本身设为nil,这样可以释放内存中的数据。现在来考虑当应用回到前台,并且代码尝试获取myBigDataReal时会发生什么。如果它不为nil,那么我们只需获取其值即可。但如果它为nil,可能是因为我们将其值保存到了磁盘上。现在我想读取磁盘来恢复其值,然后获取。这正是计算变量门面的用武之地:
var myBigData : NSData! { set (newdata) { self.myBigDataReal = newdata } get { if myBigDataReal == nil { // ... get a reference to file on disk, f ... self.myBigDataReal = NSData(contentsOfFile: f) // ... erase the file ... } return self.myBigDataReal }}