Swift的get与set与willSet与didSet详解

最近写程序越来越会偷懒了,可能也是因为对swift越来越熟悉了吧,以至于最近连if都不想写了都直接用三元运算符了。。这也是今天这篇文章的导火索,就是因为我有两个布尔变量,来判断是否是pulling和headPulling(简称p和hp),hp为true时p一定也为true,但p为true时hp就不一定为true,因为还会有footPulling的情况,你问为什么不是三个布尔变量?因为我懒啊!但我还想更懒,我完全不想去管p,而是想只改hp就可以完全满足我判断究竟是fp还是hp以及p的情况,该怎么样呢?于是我就想起来了以前看到过的get和set,但是虽然抄了过来,但具体意义什么的完全没深究过,这次就研究了一下!

 swift的计算属性

Google了一番才知道get和set这两位的术语是:Computed Properties. 即计算属性。其实从名字就可以看出来,其真正的作用是计算,而非存储。不少从OC转来的开发者可能一开始会将其按照原本的getter和setter方法来理解而导致一些疑惑。计算属性真正的用处是在于利用这两个方法间接的获取或者改变其他的变量或属性。

get

get方法在这两个方法中是必须的,意即如果要声明计算属性,则必须存在get方法,get方法的作用是在开发者用dot文法获取该变量的值的时候,返回一个固定的或经过计算的值。

set

set方法是一个可选方法,其会传入一个与所属变量类型相同的参数,这个参数的值即使用中被赋予的值,在set文法中,可以利用获取的参数的值,去计算并设定其他变量的值。如果要使用set方法,则必须包含get方法。

以下是官方开发者文档中给出的例子:

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)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"

其实只要稍微仔细看一下就可以理解get和set的用法,所需要注意的就是如下几点:

1. 无论是set还是get都不可以改变(或返回)自身的值。这个错误不会被编辑器发现,但会是运行时错误而被抛出异常。

2. set可以忽略参数声明而直接使用newValue的默认值,如上面的例子可以被改写成这样

set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}

3. 如果要声明计算属性则必须声明get方法,只声明get方法的计算属性成为只读计算属性,将来的使用中该属性无法被赋值而只能获取其在get方法中返回的值。同时,只读计算属性可以简化写法,不用再按标准方式声明get方法。例如:

struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"

以上便是计算属性的用法了,可是仔细一想就能发现,我其实需要的并不是计算属性,尽管我确实是想在我的hp改动的时候联动的改动p,这是计算属性中的set方法可以做到的事情,但是同时我并不需要get方法,但恰恰get方法又是计算属性所必须的,所以再深究一下就能发现我们这次的正题:willSet和didSet。

属性观察者

正如其名,willSet和didSet这两个方法会观察属性的值的改变,并在特定的时候做出特定的反应。每一次属性的值的改变都很调用设定好的属性观察者的方法,就算新的值和属性越来的值相同也不例外。

除了定义为lazy的存储属性外,这两个观察者方法可以在任何其他存储属性中声明。对于继承的情况,观察者方法同样适用于继承的存储属性,同时还增加了继承的计算属性,当然,你不用为不重写的父计算属性添加属性观察者,因为你可以直接在set中做到观察者所能做的事情。

同时需要注意,这两个方法之间没有必然的关系,你可以同时声明这两个方法或只声明其中任意一个。

但是这两个方法的调用时间和传入参数却有很大不同:

willSet方法会在新值被储存前调用,官方文档中使用了“just before”这个词,可能意思就是这个方法调用完了该变量就会立刻存储新值吧。同时willSet方法接收的参数是即将被存储的新值,即newValue,如果希望简化方法的话可以不书写参数而直接用dot文法调用该变量。

而didSet方法会在新值被存储后调用,官方文档中使用了“immediately”这个词,同上的猜测就是这个方法会在存储完新值后立刻调用。与willSet不同的是,didSet中的参数是改变之前值,即oldValue,同样允许简化书写。

以下是官方的例子:

class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
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

这个方法就可以完美解决我遇到的问题,我只要在hp的willSet观察者方法里直接设定p的值就好了,有关get和set以及willSet和didSet也就说得差不多了,这两个知识点并不是什么难点,但感觉真正会实用的人也是比较少的,所以特来分享一下。

1 评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注