value type & reference type 的 property observer

透過定義 Swift property observer 的 willSet & didSet,我們可以在 property 內容被設定時執行某段程式。

struct Dog {
var name: String
}

struct Baby {
var dog = Dog(name: "史努比") {
didSet {
print("小狗變了")
}
}
}

var cuteBaby = Baby()
cuteBaby.dog = Dog(name: "布丁狗")

結果

由於 cuteBaby.dog = Dog(name: "布丁狗") 利用 = 修改 property dog,所以毫無疑問地, dog 的 didSet 將被觸發執行。但如果我們修改小狗的名字呢 ? 此時 property 是 value type 還是 reference type 將深深影響 willSet & didSet 是否觸發。

1 當 property 是 value type,以 struct 定義的 Dog 為例

struct Dog {
var name: String
}

struct Baby {
var dog = Dog(name: "史努比") {
didSet {
print("小狗變了")
}
}
}

var cuteBaby = Baby()
cuteBaby.dog.name = "布丁狗"

cuteBaby.dog.name = "布丁狗" 將觸發 didSet,因為 Dog 是 value type,所以設定 Dog 的任何一個 property 都代表 property dog 改變了。

2 當 property 是 reference type,以 class 定義的 Dog 為例

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

struct Baby {
var dog = Dog(name: "史努比") {
didSet {
print("小狗變了")
}
}
}

var cuteBaby = Baby()
cuteBaby.dog.name = "布丁狗"

didSet 不會觸發。因為 Dog 是 reference type,所以 cuteBaby.dog 儲存的是小狗物件的記憶體位置,cuteBaby.dog.name = "布丁狗" 改變的是 小狗物件的 name,而不是寶寶物件的 dog。

在 UIView 的 height 改變時,更新圓角的 cornerRadius。

最後讓我們看一個開發 iOS App 時常見的例子。我們希望做個圓角的 button,當 button 高度改變時,cornerRadius 自動更新為高度的一半。

import UIKit

class RoundButton: UIButton {
override var frame: CGRect {
didSet {
layer.cornerRadius = frame.height / 2
}
}
}

let button = RoundButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.setTitle("Swift", for: .normal)
button.backgroundColor = UIColor.orange
button
button.frame.size.height = 100
button

由於 frame 的型別 CGRect 是 struct 定義的 value type,而 size 的型別 CGSize 也是 value type,所以 button.frame.size.height = 100 將改變 frame,觸發 frame 的 didSet 執行。

剛剛的程式在 Auto Layout 時也能發揮效果,因為當 Auto Layout 依據我們設定的條件計算出元件的 frame 後,也會觸發 frame 的 didSet。

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com