Swift 中的屬性觀察者

屬性觀察者,用來監聽和響應屬性值的改變。在每次屬性值被設置新值時都會被調用,即便設置的新值跟屬性的當前值如出一轍。markdown

能夠添加屬性觀察者的屬性類型:ide

  • 定義的存儲屬性。
  • 繼承的存儲屬性。
  • 繼承的計算屬性。

屬性觀察者經過下面的兩個函數來監聽:函數

  • willSet:在值存儲前被調用。
  • didSet:在值存儲後調用。

定義的存儲屬性

class Person {
    var name: String = "jack" {
        willSet(newName) {
            print("newName == \(newName)")
        }
        
        didSet {
            print("oldName == \(oldValue)")
        }
    }
}

let p = Person()
p.name = "rose" // name == jack;newName == rose name == rose;oldName == jack
複製代碼

上面的代碼定義了一個 Person 的類,該類包含一個 name 的存儲屬性。name 屬性添加了屬性觀察者,並實現了 willSet/didSet 兩個函數。spa

經過打印能夠看出,在給 name 屬性賦值以後,會先調用 willSet 打印 name == jack;newName == rose,再調用 didSet 打印 name == rose;oldName == jack。由此能夠看出 willSet 是在值存儲前被調用,而 didSet 是在值存儲後調用。code

須要注意的是,當調用構造函數的時候,不會觸發屬性觀察者。詳見下面的代碼:orm

class Person {
    var name: String {
        willSet(newName) {
            print("name == \(name);newName == \(newName)")
        }
        
        didSet {
            print("name == \(name);oldName == \(oldValue)")
        }
    }
    
    init(name: String) {
        self.name = name
    }
}

let p = Person(name: "rose")
複製代碼

上面的代碼中,將 name 屬性的初始值刪掉,並提供了一個構造函數。當調用 let p = Person(name: "rose") 這行代碼時,並無任何打印。說明調用構造函數並不會觸發屬性觀察者。繼承

繼承的存儲屬性

class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

class Student: Person {
    override var name: String {
        willSet(newName) {
            print("name == \(name);newName == \(newName)")
        }
        
        didSet {
            print("name == \(name);oldName == \(oldValue)")
        }
    }
}

let stu = Student(name: "jack")
stu.name = "rose" // name == jack;newName == rose name == rose;oldName == jack
複製代碼

上面的代碼聲明瞭一個 Student 的類,繼承自 Person。並對Student 繼承 的 name 屬性添加了屬性觀察者。get

經過打印能夠看出,在給 name 屬性賦值以後,會先調用 willSet 打印 name == jack;newName == rose,再調用 didSet 打印 name == rose;oldName == jack編譯器

雖然,調用父類構造函數不會觸發屬性觀察者,但若是在子類中修改屬性值,是會觸發屬性觀察者的。詳見下面的代碼:it

class Person {
    var name: String {
        willSet(newName) {
            print("name == \(name);newName == \(newName)")
        }
        
        didSet {
            print("name == \(name);oldName == \(oldValue)")
        }
    }
    
    init(name: String) {
        self.name = name
        self.name = "Person, \(name)"
    }
}

class Student: Person {
    override init(name: String) {
        super.init(name: name)
        self.name = "Student, \(name)"
    }
}

let p = Person(name: "mike") // 沒有任何打印
let stu = Student(name: "robin") 
// name == Person, robin;newName == Student, robin
// name == Student, robin;oldName == Person, robin
複製代碼

能夠看到,在 Student 的構造器中添加 self.name = "Student, \(name)" 以後,就會觸發屬性觀察者。

而在 Person 的構造器中,不管添加多少 self.name = "Person, \(name)" ,都不會觸發屬性觀察者。

繼承的計算屬性

struct BodyInfo {
    var height = 0
    var weight = 0
}

class Person {
    var name: String
    var body = BodyInfo()
    
    var info: BodyInfo {
        get {
            return BodyInfo(height: body.height + 1, weight: body.weight + 1)
        }
        
        set {
            body.height = newValue.height + 1
            body.weight = newValue.weight + 1
        }
    }
    
    init(name: String) {
        self.name = name
    }
}

class Student: Person {
    override var info: BodyInfo {
        willSet(newInfo) {
            print("newInfo = \(newInfo)")
        }
        
        didSet {
            print("oldInfo = \(oldValue)")
        }
    }
}


let stu = Student(name: "jack")
stu.info = BodyInfo(height: 15, weight: 20)
// newInfo = BodyInfo(height: 15, weight: 20)
// oldInfo = BodyInfo(height: 1, weight: 1)
複製代碼

須要說明上面的代碼邏輯上狗屁不通,只是當作代碼說明🤣。

上述代碼中,Person 定義了一個 info 的計算屬性,而後 Student 中繼承了 info,並給它添加了屬性觀察者。

經過 stu.info = BodyInfo(height: 15, weight: 20) 的調用,能夠看到輸出的結果是符合預期的。

須要說明的是,不能再 Person 中給 info 添加屬性觀察者,由於 willSet/didSet 是不能和 get 同時出現的,感興趣的同窗能夠本身動手實踐一下。編譯器會報錯:'willSet' cannot be provided together with a getter

總結

  • 能夠添加屬性觀察者的屬性類型: 定義的存儲屬性/繼承的存儲屬性/繼承的計算屬性。
  • 屬性觀察者的兩個函數:*willSet:在值存儲前被調用;didSet:在值存儲後調用。
  • 調用本類的構造函數不會觸發屬性觀察者,但在子類中的構造器中修改屬性會觸發。
相關文章
相關標籤/搜索