计算变量并不需要成为存储变量门面,这一点与你想的可能会不同。这是因为Swift提供了另一个漂亮的特性,可以让你将功能注入存储变量的setter中,即setter观察者。这些函数会在其他代码设置存储变量前后被调用。
声明具有setter观察者变量的语法非常类似于声明计算变量的语法;你可以编写一个willSet函数、一个didSet函数,二者也可以都提供:
var s = "whatever" { ① willSet { ② print(newValue) ③ } didSet { ④ print(oldValue) ⑤ // self.s = "something else" }}
①变量必须是var(不能是let)。可以为它赋初值,后跟一对花括号。
②willSet函数,如果有,那就是willSet,后跟一对花括号,里面是函数体。当其他代码设置该变量时它会被调用,就在变量接收到新值之前。
③在默认情况下,willSet函数会将接收到的新值设为newValue。你可以在单词willSet后面的圆括号中提供不同的名字来改变这一点。旧值依然位于存储变量中,willSet函数可以访问到它。
④didSet函数,如果有,那就是didSet,后跟一对花括号,里面是函数体。当其他代码设置该变量时它会被调用,就在变量接收到新值之后。
⑤在默认情况下,didSet函数会接收到旧值,它已经被变量值所替换,名字为oldValue。你可以在单词didSet后面的圆括号中提供不同的名字来改变这一点。新值已经位于存储变量中,didSet函数可以访问到它。此外,didSet函数也可以将存储变量设为不同的值。
如果存储变量被初始化了或是didSet函数修改了存储变量值,那么Setter观察者函数就不会被调用,这是个循环!
实际上,在使用Objective-C中的Setter重写的大多数情况下,相对于计算变量来说,我更倾向于使用Setter观察者。如下示例来自于Apple提供的代码(Master–Detail Application模板),它说明了一种典型场景,即在设置了某个属性后改变界面:
var detailItem: AnyObject? { didSet { // Update the view. self.configureView }}
这是视图控制器类的一个实例属性。每次修改该属性时,我们都需要改变界面,因为界面的一部分职责是显示该属性的值。因此,每次设置属性时,我们只需调用一个实例方法即可。该实例方法会读取属性值并相应地设置界面。
如下示例来自于我所编写的代码,我们不仅要修改界面,还要将设置的值限定在一个范围内:
var angle : CGFloat = 0 { didSet { // angle must not be smaller than 0 or larger than 5 if self.angle < 0 { self.angle = 0 } if self.angle > 5 { self.angle = 5 } // modify interface to match self.transform = CGAffineTransformMakeRotation(self.angle) }}
计算变量是没有Setter观察者的,它也不需要!有一个Setter函数,在设置值时的一些额外处理可以直接在该Setter函数中以编程的方式完成。