Swift中willSet和didSet的作用是什么?


265

Swift具有类似于C#的属性声明语法:

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

但是,它也具有willSetdidSet动作。这些分别在设置器被调用之前和之后被调用。考虑到setter内可能只有相同的代码,它们的目的是什么?


11
我个人不喜欢这里的许多答案。他们过多地介绍了语法。区别更多在于语义和代码的可读性。计算属性(getset)基本上应具有基于另一个属性计算的属性,例如,将标签text转换为year IntdidSetwillSet会说...嘿,这个值已经设置好了,现在让我们开始吧,例如我们的dataSource已更新...所以让我们重新加载tableView使其包含新行。有关另一个示例,请参见dfri关于如何在didSet
Honey

Answers:


324

重点似乎是,有时您需要一个具有自动存储某些行为的属性,例如,通知其他对象该属性刚刚更改。当所有是get/时set,您需要另一个字段来保存值。使用willSetdidSet,可以在修改值时采取措施,而无需其他字段。例如,在该示例中:

class Foo {
    var myProperty: Int = 0 {
        didSet {
            print("The value of myProperty changed from \(oldValue) to \(myProperty)")
        }
    }
}

myProperty每次修改时都会打印其新旧值。仅使用getter和setter,我将需要以下内容:

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代表了几行的经济性,并且字段列表中的噪音更少。


248
注意:如Apple所述,当您从init方法中设置属性时willSetdidSet不会调用和:willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
Klaas 2014年

4
但是在执行此操作时,它们似乎是在数组属性上调用的:myArrayProperty.removeAtIndex(myIndex)...不期望。
安德烈亚斯(Andreas)

4
您可以将赋值包装在初始化程序内的defer {}语句中,这会导致在退出初始化程序范围时调用willSet和didSet方法。我不一定会推荐它,只是说这是可能的。结果之一是,仅当您声明该属性为可选时,该属性才起作用,因为未严格从初始化程序对其进行初始化。
Marmoy

请在下面的行中说明。我没有得到,是这个方法还是变量var propertyChangedListener:(Int,Int)-> Void = {println(“ myProperty的值从($ 0)变为($ 1)”)}
Vikash Rajput

在同一行初始化特性在雨燕3.不支持您应该更改答案,以符合SWIFT 3
拉马赞·波拉特

149

我的理解是set和get用于计算的属性(没有存储属性的支持)

如果您来自Objective-C,则请记住命名约定已更改。在Swift中,iVar或实例变量称为存储属性

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

请注意两个计算和存储属性的使用。我使用了计算属性来防止两次设置相同的值(以避免发生坏事!);我已经使用willSet和didSet将通知转发到viewControllers(请参阅UIViewController文档和有关viewController容器的信息)

希望对您有所帮助,如果我在这里的任何地方犯了错误,请大声喊叫!


3
为什么不能使用我将didSet与get和set ..一起使用?
Ben Sinclair 2014年

//I can't see a way to 'stop' the value being set to the same controller - hence the computed propertyif let newViewController = _childVC { 代替 使用后警告消失 if (_childVC) {
evfemist 2015年

5
get和set用于创建计算属性。这些纯粹是方法,没有后备存储(实例变量)。willSet和didSet用于观察对存储变量属性的更改。在底层,这些都由存储支持,但是在Swift中,它们全部融合为一个。
user3675131'2

在示例5中get,我认为您需要先添加if _childVC == nil { _childVC = something },然后再添加return _childVC
JW.ZG

18

这些称为“ 属性观察者”

财产观察员观察并响应财产价值的变化。每次设置属性值时都会调用属性观察器,即使新值与属性的当前值相同也是如此。

摘录自:苹果公司“ The Swift Programming Language”。iBooks。https://itun.es/ca/jEUH0.l

我怀疑这是允许我们传统上使用KVO做的事情,例如与UI元素的数据绑定,或触发更改属性,触发同步过程,后台处理等副作用。



16

您也可以使用将didSet变量设置为其他值。这不会导致如属性指南中所述再次调用观察者。例如,当您要限制以下值时,它很有用:

let minValue = 1

var value = 1 {
    didSet {
        if value < minValue {
            value = minValue
        }
    }
}

value = -10 // value is minValue now.

10

现有的许多写得很好的答案都很好地涵盖了这个问题,但是我会更详细地提及我认为值得补充的内容。


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

之后,可以在某些视图控制器中使用您的委托函数,以观察for中模型的关键变化CustomViewController,就像您将使用UITextFieldDelegatefor UITextField对象的固有委托函数(例如textFieldDidEndEditing(...))一样。

对于此简单示例,请使用didSetclass属性的委托回调value来告诉视图控制器其出口之一已关联了模型更新:

// 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属性已被封装,但是通常:在这种情况下,请注意不要在视图控制器中的关联委托函数(此处为)的范围内更新对象的value属性,否则最终无限递归。customUserControldidChangeValue()


4

每当为属性分配新值时,它们的willSet和didSet观察者。即使新值与当前值相同,也是如此。

请注意,willSet另一方面,didSet不需要参数名称即可解决。

属性值更新后,将调用didSet观察器。它与旧值进行比较。如果步骤总数增加,则会显示一条消息,指示已执行了多少个新步骤。didSet观察器没有为旧值提供自定义参数名称,而是使用了默认名称oldValue。


2

有时,getter和setter太重了,以至于无法执行以观察正确的值更改。通常,这需要额外的临时变量处理和额外的检查,并且如果您编写数百个getter和setter的话,您甚至会希望避免那些琐碎的工作。这些东西适合这种情况。


1
您是说使用和与等效的设置程序代码相比在性能上有优势吗?这似乎是一个大胆的主张。willSetdidSet
zneak 2014年

1
@zneak我使用了错误的单词。我要求程序员付出努力,而不是处理费用。
Eonil 2014年

1

在您自己的(基)类中,它非常多余,因为您可以定义一个访问a 并执行所需的前后处理的计算属性(即getwillSetset方法)。didSet_propertyVariable

如果,但是,您可以覆盖其属性是一类已经定义那么willSetdidSet有用的,没有多余的!


1

didSet真正方便的一件事是使用插座添加其他配置。

@IBOutlet weak var loginOrSignupButton: UIButton! {
  didSet {
        let title = NSLocalizedString("signup_required_button")
        loginOrSignupButton.setTitle(title, for: .normal)
        loginOrSignupButton.setTitle(title, for: .highlighted)
  }

或使用willSet对此插座方法有意义,不是吗?
elia

-5

我不懂C#,但我有点猜测,我想我明白了

foo : int {
    get { return getFoo(); }
    set { setFoo(newValue); }
}

做。它看起来与您在Swift中非常相似,但是并不相同:在Swift中您没有getFooand setFoo。差别不小:这意味着您没有任何基础存储可用于其价值。

Swift已存储并计算了属性。

计算属性具有get和可能具有set(如果可写)。但是,如果getter和setter中的代码需要实际存储一些数据,则必须在其他代码中使用它属性中执行。没有后备存储。

另一方面,存储的属性确实具有后备存储。但它并没有具备getset。相反,它有willSetdidSet你可以用它来观察变量的变化,并最终触发的副作用和/或修改存储的值。您没有willSetdidSet对于计算的属性,也不需要它们,因为对于计算的属性,可以使用代码set来控制更改。


这是Swift的例子。getFoo并且setFoo是简单的占位符,可用于您希望使用getter和setter进行的任何操作。C#也不需要它们。(在访问编译器之前,我确实想念了一些语法上的微妙之处。)
zneak

1
哦好的。但重要的一点是,计算属性没有基础存储。另请参阅我的其他答案:stackoverflow.com/a/24052566/574590
模拟文件
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.