屬性觀察 (Property Observers) 是 Swift 中一個(gè)很特殊的特性,利用屬性觀察我們可以在當(dāng)前類型內(nèi)監(jiān)視對(duì)于屬性的設(shè)定,并作出一些響應(yīng)。Swift 中為我們提供了兩個(gè)屬性觀察的方法,它們分別是 willSet 和 didSet。
使用這兩個(gè)方法十分簡(jiǎn)單,我們只要在屬性聲明的時(shí)候添加相應(yīng)的代碼塊,就可以對(duì)將要設(shè)定的值和已經(jīng)設(shè)置的值進(jìn)行監(jiān)聽了:
class MyClass {
var date: NSDate {
willSet {
let d = date
print("即將將日期從 \(d) 設(shè)定至 \(newValue)")
}
didSet {
print("已經(jīng)將日期從 \(oldValue) 設(shè)定至 \(date)")
}
}
init() {
date = NSDate()
}
}
let foo = MyClass()
foo.date = foo.date.dateByAddingTimeInterval(10086)
// 輸出
// 即將將日期從 2014-08-23 12:47:36 +0000 設(shè)定至 2014-08-23 15:35:42 +0000
// 已經(jīng)將日期從 2014-08-23 12:47:36 +0000 設(shè)定至 2014-08-23 15:35:42 +0000
在 willSet 和 didSet 中我們分別可以使用 newValue 和 oldValue 來(lái)獲取將要設(shè)定的和已經(jīng)設(shè)定的值。屬性觀察的一個(gè)重要用處是作為設(shè)置值的驗(yàn)證,比如上面的例子中我們不希望 date 超過(guò)當(dāng)前時(shí)間的一年以上的話,我們可以將 didSet 修改一下:
class MyClass {
let oneYearInSecond: TimeInterval = 365 * 24 * 60 * 60
var date: NSDate {
//...
didSet {
if (date.timeIntervalSinceNow > oneYearInSecond) {
print("設(shè)定的時(shí)間太晚了!")
date = NSDate().addingTimeInterval(oneYearInSecond)
}
print("已經(jīng)將日期從 \(oldValue) 設(shè)定至 \(date)")
}
}
//...
}
更改一下調(diào)用,我們就能看到效果:
// 365 * 24 * 60 * 60 = 31_536_000
foo.date = foo.date.dateByAddingTimeInterval(100_000_000)
// 輸出
// 即將將日期從 2014-08-23 13:24:14 +0000 設(shè)定至 2017-10-23 23:10:54 +0000
// 設(shè)定的時(shí)間太晚了!
// 已經(jīng)將日期從 2014-08-23 13:24:14 +0000 設(shè)定至 2015-08-23 13:24:14 +0000
初始化方法對(duì)屬性的設(shè)定,以及在 willSet 和 didSet 中對(duì)屬性的再次設(shè)定都不會(huì)再次觸發(fā)屬性觀察的調(diào)用,一般來(lái)說(shuō)這會(huì)是你所需要的行為,可以放心使用能夠。
我們知道,在 Swift 中所聲明的屬性包括存儲(chǔ)屬性和計(jì)算屬性兩種。其中存儲(chǔ)屬性將會(huì)在內(nèi)存中實(shí)際分配地址對(duì)屬性進(jìn)行存儲(chǔ),而計(jì)算屬性則不包括背后的存儲(chǔ),只是提供 set 和 get 兩種方法。在同一個(gè)類型中,屬性觀察和計(jì)算屬性是不能同時(shí)共存的。也就是說(shuō),想在一個(gè)屬性定義中同時(shí)出現(xiàn) set 和 willSet 或 didSet 是一件辦不到的事情。計(jì)算屬性中我們可以通過(guò)改寫 set 中的內(nèi)容來(lái)達(dá)到和 willSet 及 didSet 同樣的屬性觀察的目的。如果我們無(wú)法改動(dòng)這個(gè)類,又想要通過(guò)屬性觀察做一些事情的話,可能就需要子類化這個(gè)類,并且重寫它的屬性了。重寫的屬性并不知道父類屬性的具體實(shí)現(xiàn)情況,而只從父類屬性中繼承名字和類型,因此在子類的重載屬性中我們是可以對(duì)父類的屬性任意地添加屬性觀察的,而不用在意父類中到底是存儲(chǔ)屬性還是計(jì)算屬性:
class A {
var number :Int {
get {
print("get")
return 1
}
set {print("set")}
}
}
class B: A {
override var number: Int {
willSet {print("willSet")}
didSet {print("didSet")}
}
}
調(diào)用 number 的 set 方法可以看到工作的順序
let b = B()
b.number = 0
// 輸出
// get
// willSet
// set
// didSet
set 和對(duì)應(yīng)的屬性觀察的調(diào)用都在我們的預(yù)想之中。這里要注意的是 get 首先被調(diào)用了一次。這是因?yàn)槲覀儗?shí)現(xiàn)了 didSet,didSet 中會(huì)用到 oldValue,而這個(gè)值需要在整個(gè) set 動(dòng)作之前進(jìn)行獲取并存儲(chǔ)待用,否則將無(wú)法確保正確性。如果我們不實(shí)現(xiàn) didSet 的話,這次 get 操作也將不存在。