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

 青枫望 发布于 2023-01-12 07:38

Swift有一个非常类似于C#的属性声明语法:

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

但是,它也有willSetdidSet行动.这些在分别调用setter之前和之后调用.考虑到你可以在setter中使用相同的代码,它们的目的是什么?

6 个回答
  • 我的理解是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容器上的信息)

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

    2023-01-12 07:41 回答
  • 这些被称为Property Observers:

    财产观察员观察并回应财产价值的变化.每次设置属性值时都会调用属性观察者,即使新值与属性的当前值相同.

    摘录自:Apple Inc."The Swift Programming Language."iBooks.https://itun.es/ca/jEUH0.l

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

    2023-01-12 07:42 回答
  • 注意

    willSetdidSet代表团发生前,没有当属性在初始化设置所谓的观察家

    2023-01-12 07:42 回答
  • 关键在于,有时候,您需要一个具有自动存储某些行为的属性,例如通知其他对象该属性刚刚更改.当你拥有的是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
            }
        }
    }
    

    因此willSet,didSet代表了几行的经济,并且在现场列表中的噪音更少.

    2023-01-12 07:43 回答
  • 您还可以使用将didSet变量设置为其他值.这不会导致按照属性指南中的说明再次调用观察者.例如,当您想要将值限制如下时,它非常有用:

    let minValue = 1
    
    var value = 1 {
        didSet {
            if value < minValue {
                value = minValue
            }
        }
    }
    
    value = -10 // value is minValue now.
    

    2023-01-12 07:47 回答
  • 许多写得很好的现有答案很好地涵盖了这个问题,但我会详细地提到一个我认为值得报道的补充.


    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,就像您使用UITextFieldDelegatefor UITextField对象的固有委托函数(例如textFieldDidEndEditing(...))一样.

    对于这个简单的示例,使用来自didSet类属性的委托回调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()

    2023-01-12 07:50 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有