屬性觀察 (Property Observers) 是 Swift 中一個很特殊的特性溃论,利用屬性觀察我們可以在當(dāng)前類型內(nèi)監(jiān)視對于屬性的設(shè)定年扩,并作出一些響應(yīng)饭于。Swift 中為我們提供了兩個屬性觀察的方法澜汤,它們分別是 willSet 和 didSet。
使用這兩個方法十分簡單唇辨,我們只要在屬性聲明的時候添加相應(yīng)的代碼塊鸯绿,就可以對將要設(shè)定的值和已經(jīng)設(shè)置的值進行監(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 來獲取將要設(shè)定的和已經(jīng)設(shè)定的值拇厢。屬性觀察的一個重要用處是作為設(shè)置值的驗證满粗,比如上面的例子中我們不希望 date 超過當(dāng)前時間的一年以上的話辈末,我們可以將 didSet 修改一下:
class MyClass {
let oneYearInSecond: TimeInterval = 365 * 24 * 60 * 60
var date: NSDate {
//...
didSet {
if (date.timeIntervalSinceNow > oneYearInSecond) {
print("設(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è)定的時間太晚了挤聘!
// 已經(jīng)將日期從 2014-08-23 13:24:14 +0000 設(shè)定至 2015-08-23 13:24:14 +0000
初始化方法對屬性的設(shè)定,以及在 willSet 和 didSet 中對屬性的再次設(shè)定都不會再次觸發(fā)屬性觀察的調(diào)用劫扒,一般來說這會是你所需要的行為檬洞,可以放心使用能夠。
我們知道沟饥,在 Swift 中所聲明的屬性包括存儲屬性和計算屬性兩種。其中存儲屬性將會在內(nèi)存中實際分配地址對屬性進行存儲,而計算屬性則不包括背后的存儲贤旷,只是提供 set 和 get 兩種方法广料。在同一個類型中,屬性觀察和計算屬性是不能同時共存的幼驶。也就是說艾杏,想在一個屬性定義中同時出現(xiàn) set 和 willSet 或 didSet 是一件辦不到的事情。計算屬性中我們可以通過改寫 set 中的內(nèi)容來達到和 willSet 及 didSet 同樣的屬性觀察的目的盅藻。如果我們無法改動這個類购桑,又想要通過屬性觀察做一些事情的話,可能就需要子類化這個類氏淑,并且重寫它的屬性了勃蜘。重寫的屬性并不知道父類屬性的具體實現(xiàn)情況,而只從父類屬性中繼承名字和類型假残,因此在子類的重載屬性中我們是可以對父類的屬性任意地添加屬性觀察的缭贡,而不用在意父類中到底是存儲屬性還是計算屬性:
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 和對應(yīng)的屬性觀察的調(diào)用都在我們的預(yù)想之中。這里要注意的是 get 首先被調(diào)用了一次辉懒。這是因為我們實現(xiàn)了 didSet阳惹,didSet 中會用到 oldValue,而這個值需要在整個 set 動作之前進行獲取并存儲待用眶俩,否則將無法確保正確性莹汤。如果我們不實現(xiàn) didSet 的話,這次 get 操作也將不存在颠印。