willSet和didSet

屬性觀察 (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 操作也將不存在。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容