[英]What is the purpose of willSet and didSet in Swift?
Swift有一個非常類似於C#的屬性聲明語法:
var foo: Int {
get { return getFoo() }
set { setFoo(newValue) }
}
但是,它還具有willSet
和didSet
操作。 這些在分別調用setter之前和之后調用。 考慮到你可以在setter中使用相同的代碼,它們的目的是什么?
關鍵在於,有時候,您需要一個具有自動存儲和某些行為的屬性,例如通知其他對象該屬性剛剛更改。 當你已經是get
/ set
,你需要另一個字段來保存的價值。 使用willSet
和didSet
,您可以在修改值時執行操作,而無需其他字段。 例如,在該示例中:
class Foo {
var myProperty: Int = 0 {
didSet {
print("The value of myProperty changed from \(oldValue) to \(myProperty)")
}
}
}
myProperty
每次修改時都會打印舊值和新值。 只有吸氣劑和二傳手,我需要這樣做:
class Foo {
var myPropertyValue: Int = 0
var myProperty: Int {
get { return myPropertyValue }
set {
print("The value of myProperty changed from \(myPropertyValue) to \(newValue)")
myPropertyValue = newValue
}
}
}
所以willSet
和didSet
代表了幾行的經濟性,而且字段列表中的噪音更少。
我的理解是set和get是針對計算屬性的 (沒有來自存儲屬性的支持)
如果你是來自Objective-C,請記住命名約定已經改變。 在Swift中,iVar或實例變量被命名為stored屬性
var test : Int {
get {
return test
}
}
這將導致警告,因為這會導致遞歸函數調用(getter調用自身)。在這種情況下的警告是“嘗試在其自己的getter中修改'test'”。
var test : Int {
get {
return test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
//(prevents same value being set)
if (aNewValue != test) {
test = aNewValue
}
}
}
類似的問題 - 你不能這樣做,因為它遞歸調用setter。 此外,請注意,此代碼不會抱怨沒有初始化者,因為沒有存儲屬性可以初始化 。
這是一種允許條件設置實際存儲屬性的模式
//True model data
var _test : Int = 0
var test : Int {
get {
return _test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
if (aNewValue != test) {
_test = aNewValue
}
}
}
注意實際數據稱為_test(盡管它可以是任何數據或數據組合)注意還需要提供初始值(或者您需要使用init方法),因為_test實際上是一個實例變量
//True model data
var _test : Int = 0 {
//First this
willSet {
println("Old value is \(_test), new value is \(newValue)")
}
//value is set
//Finaly this
didSet {
println("Old value is \(oldValue), new value is \(_test)")
}
}
var test : Int {
get {
return _test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
if (aNewValue != test) {
_test = aNewValue
}
}
}
在這里,我們看到willSet和didSet攔截了實際存儲屬性的變化。 這對於發送通知,同步等非常有用...(參見下面的示例)
//Underlying instance variable (would ideally be private)
var _childVC : UIViewController? {
willSet {
//REMOVE OLD VC
println("Property will set")
if (_childVC != nil) {
_childVC!.willMoveToParentViewController(nil)
self.setOverrideTraitCollection(nil, forChildViewController: _childVC)
_childVC!.view.removeFromSuperview()
_childVC!.removeFromParentViewController()
}
if (newValue) {
self.addChildViewController(newValue)
}
}
//I can't see a way to 'stop' the value being set to the same controller - hence the computed property
didSet {
//ADD NEW VC
println("Property did set")
if (_childVC) {
// var views = NSDictionaryOfVariableBindings(self.view) .. NOT YET SUPPORTED (NSDictionary bridging not yet available)
//Add subviews + constraints
_childVC!.view.setTranslatesAutoresizingMaskIntoConstraints(false) //For now - until I add my own constraints
self.view.addSubview(_childVC!.view)
let views = ["view" : _childVC!.view] as NSMutableDictionary
let layoutOpts = NSLayoutFormatOptions(0)
let lc1 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
self.view.addConstraints(lc1)
self.view.addConstraints(lc2)
//Forward messages to child
_childVC!.didMoveToParentViewController(self)
}
}
}
//Computed property - this is the property that must be used to prevent setting the same value twice
//unless there is another way of doing this?
var childVC : UIViewController? {
get {
return _childVC
}
set(suggestedVC) {
if (suggestedVC != _childVC) {
_childVC = suggestedVC
}
}
}
注意使用BOTH計算和存儲的屬性。 我已經使用了一個計算屬性來防止設置兩次相同的值(以避免發生壞事!); 我使用了willSet和didSet將通知轉發給viewControllers(請參閱UIViewController文檔和viewController容器上的信息)
我希望這會有所幫助,如果我在這里任何地方犯了錯誤,請有人大聲喊叫!
這些被稱為Property Observers :
財產觀察員觀察並回應財產價值的變化。 每次設置屬性值時都會調用屬性觀察者,即使新值與屬性的當前值相同。
摘錄自:Apple Inc.“The Swift Programming Language。”iBooks。 https://itun.es/ca/jEUH0.l
我懷疑這是允許傳統上用KVO做的事情,例如與UI元素的數據綁定,或觸發更改屬性,觸發同步過程,后台處理等的副作用等。
您還可以使用didSet
將變量設置為其他值。 這不會導致按照屬性指南中的說明再次調用觀察者。 例如,當您想要將值限制如下時,它非常有用:
let minValue = 1
var value = 1 {
didSet {
if value < minValue {
value = minValue
}
}
}
value = -10 // value is minValue now.
注意
在委派發生之前在初始化程序中設置屬性時,不會調用
willSet
和didSet
觀察者
許多寫得很好的現有答案很好地涵蓋了這個問題,但我會詳細地提到一個我認為值得報道的補充。
willSet
和didSet
屬性觀察器可用於調用委托,例如,用於僅通過用戶交互更新的類屬性,但是您希望避免在對象初始化時調用委托。
我會引用Klaas對已接受的答案進行評論:
首次初始化屬性時,不會調用willSet和didSet觀察者。 僅在屬性的值設置在初始化上下文之外時才調用它們。
這是非常簡潔的,因為它意味着例如,對於您自己的自定義類, didSet
屬性是委托回調和函數的啟動點的一個很好的選擇。
作為示例,考慮一些自定義用戶控件對象,具有一些關鍵屬性value
(例如,在評級控件中的位置),作為UIView
的子類實現:
// CustomUserControl.swift
protocol CustomUserControlDelegate {
func didChangeValue(value: Int)
// func didChangeValue(newValue: Int, oldValue: Int)
// func didChangeValue(customUserControl: CustomUserControl)
// ... other more sophisticated delegate functions
}
class CustomUserControl: UIView {
// Properties
// ...
private var value = 0 {
didSet {
// Possibly do something ...
// Call delegate.
delegate?.didChangeValue(value)
// delegate?.didChangeValue(value, oldValue: oldValue)
// delegate?.didChangeValue(self)
}
}
var delegate: CustomUserControlDelegate?
// Initialization
required init?(...) {
// Initialise something ...
// E.g. 'value = 1' would not call didSet at this point
}
// ... some methods/actions associated with your user control.
}
之后你的委托函數可以用在一些視圖控制器中,以觀察CustomViewController
模型中的關鍵變化,就像你使用UITextFieldDelegate
的UITextField
對象的固有委托函數(例如textFieldDidEndEditing(...)
) 。
對於這個簡單的示例,使用來自類屬性value
的didSet
的委托回調來告訴視圖控制器其中一個出口已經關聯了模型更新:
// ViewController.swift
Import UIKit
// ...
class ViewController: UIViewController, CustomUserControlDelegate {
// Properties
// ...
@IBOutlet weak var customUserControl: CustomUserControl!
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Custom user control, handle through delegate callbacks.
customUserControl = self
}
// ...
// CustomUserControlDelegate
func didChangeValue(value: Int) {
// do some stuff with 'value' ...
}
// func didChangeValue(newValue: Int, oldValue: Int) {
// do some stuff with new as well as old 'value' ...
// custom transitions? :)
//}
//func didChangeValue(customUserControl: CustomUserControl) {
// // Do more advanced stuff ...
//}
}
這里, value
屬性已被封裝,但通常是:在這種情況下,注意不要在視圖控制器中更新關聯委托函數(此處: didChangeValue()
)范圍內的customUserControl
對象的value
屬性,或者你最終會得到無限的遞歸。
每當為屬性分配新值時,willSet和didSet屬性的觀察者。 即使新值與當前值相同,也是如此。
請注意, willSet
需要一個參數名來解決,另一方面, didSet
不需要。
更新屬性值后調用didSet觀察者。 它與舊值進行比較。 如果步驟總數增加,則會打印一條消息,指示已執行了多少新步驟。 didSet觀察者不為舊值提供自定義參數名稱,而是使用默認名稱oldValue。
Getter和setter有時太重,無法實現只是為了觀察正確的值變化。 通常這需要額外的臨時變量處理和額外檢查,如果你寫了數百個getter和setter,你甚至想要避免那些微小的勞動。 這些東西是針對這種情況的。
在您自己的(基類)中, willSet
和didSet
非常簡潔 ,因為您可以定義一個計算屬性(即get-和set-方法)來訪問_propertyVariable
並執行所需的前后處理 。
但是 ,如果您覆蓋已定義屬性的類, 那么 willSet
和didSet
是有用的而不是多余的!
didSet
非常方便的一點是當你使用outlet添加額外的配置時。
@IBOutlet weak var loginOrSignupButton: UIButton! {
didSet {
let title = NSLocalizedString("signup_required_button")
loginOrSignupButton.setTitle(title, for: .normal)
loginOrSignupButton.setTitle(title, for: .highlighted)
}
我不知道C#,但有一點猜測,我想我明白了什么
foo : int {
get { return getFoo(); }
set { setFoo(newValue); }
}
確實。 它看起來與你在Swift中的相似,但它不一樣:在Swift中你沒有getFoo
和setFoo
。 這沒有什么區別:它意味着您的價值沒有任何底層存儲。
Swift存儲並計算了屬性。
計算屬性有get
並且可能已set
(如果它是可寫的)。 但是getter和setter中的代碼,如果需要實際存儲一些數據,必須在其他屬性中執行。 沒有后備存儲。
另一方面,存儲的屬性確實具有后備存儲。 但它並沒有 get
和set
。 相反,它具有willSet
和didSet
,您可以使用它來觀察變量,最終觸發副作用和/或修改存儲的值。 您沒有用於計算屬性的willSet
和didSet
,並且您不需要它們,因為對於計算屬性,您可以使用set
的代碼來控制更改。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.