簡體   English   中英

在Swift中使用willSet和didSet的目的是什么?

[英]What is the purpose of willSet and didSet in Swift?

Swift有一個非常類似於C#的屬性聲明語法:

var foo: Int {
    get { return getFoo() }
    set { setFoo(newValue) }
}

但是,它還具有willSetdidSet操作。 這些在分別調用setter之前和之后調用。 考慮到你可以在setter中使用相同的代碼,它們的目的是什么?

關鍵在於,有時候,您需要一個具有自動存儲某些行為的屬性,例如通知其他對象該屬性剛剛更改。 當你已經是get / set ,你需要另一個字段來保存的價值。 使用willSetdidSet ,您可以在修改值時執行操作,而無需其他字段。 例如,在該示例中:

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
        }
    }
}

所以willSetdidSet代表了幾行的經濟性,而且字段列表中的噪音更少。

我的理解是set和get是針對計算屬性的 (沒有來自存儲屬性的支持)

如果你是來自Objective-C,請記住命名約定已經改變。 在Swift中,iVar或實例變量被命名為stored屬性

示例1(只讀屬性) - 帶警告:

var test : Int {
    get {
        return test
    }
}

這將導致警告,因為這會導致遞歸函數調用(getter調用自身)。在這種情況下的警告是“嘗試在其自己的getter中修改'test'”。

示例2.條件讀/寫 - 帶警告

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。 此外,請注意,此代碼不會抱怨沒有初始化者,因為沒有存儲屬性可以初始化

示例3.讀/寫計算屬性 - 使用后備存儲

這是一種允許條件設置實際存儲屬性的模式

//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實際上是一個實例變量

示例4.使用will和did set

//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攔截了實際存儲屬性的變化。 這對於發送通知,同步等非常有用...(參見下面的示例)

示例5.具體示例 - ViewController容器

//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.

注意

在委派發生之前在初始化程序中設置屬性時,不會調用willSetdidSet觀察者

許多寫得很好的現有答案很好地涵蓋了這個問題,但我會詳細地提到一個我認為值得報道的補充。


willSetdidSet屬性觀察器可用於調用委托,例如,用於僅通過用戶交互更新的類屬性,但是您希望避免在對象初始化時調用委托。

我會引用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模型中的關鍵變化,就像你使用UITextFieldDelegateUITextField對象的固有委托函數(例如textFieldDidEndEditing(...) ) 。

對於這個簡單的示例,使用來自類屬性valuedidSet的委托回調來告訴視圖控制器其中一個出口已經關聯了模型更新:

// 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,你甚至想要避免那些微小的勞動。 這些東西是針對這種情況的。

在您自己的(基類)中, willSetdidSet非常簡潔 ,因為您可以定義一個計算屬性(即get-和set-方法)來訪問_propertyVariable並執行所需的前后處理

但是 ,如果您覆蓋已定義屬性的類, 那么 willSetdidSet有用的而不是多余的!

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中你沒有getFoosetFoo 這沒有什么區別:它意味着您的價值沒有任何底層存儲。

Swift存儲並計算了屬性。

計算屬性有get並且可能已set (如果它是可寫的)。 但是getter和setter中的代碼,如果需要實際存儲一些數據,必須在其他屬性中執行。 沒有后備存儲。

另一方面,存儲的屬性確實具有后備存儲。 但它並沒有 getset 相反,它具有willSetdidSet ,您可以使用它來觀察變量,最終觸發副作用和/或修改存儲的值。 您沒有用於計算屬性的willSetdidSet ,並且您不需要它們,因為對於計算屬性,您可以使用set的代碼來控制更改。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM