Swift的属性声明语法与c#非常相似:

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

但是,它也有willSet和didSet动作。它们分别在调用setter之前和之后调用。考虑到在setter中可以有相同的代码,它们的目的是什么?


当前回答

我不懂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中的代码来控制更改。

其他回答

这些被称为属性观察员:

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

摘自:苹果公司《快速编程语言》。“iBooks。https://itun.es/ca/jEUH0.l

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

我的理解是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。读取/写入计算属性-带有备份存储

Here is a pattern that allows conditional setting of an actual stored property
//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来转发通知到viewController(参见UIViewController文档和viewController容器的信息)

如果我在任何地方犯了错误,请编辑修改!

didSet非常方便的一件事是当您使用outlet添加额外配置时。

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

在你自己的(基)类中,willSet和didSet是相当多余的,因为你可以定义一个计算属性(即get-和set-方法)来访问_propertyVariable并进行所需的前后处理。

然而,如果你重写一个已经定义了属性的类,那么willSet和didSet是有用的,而不是多余的!

我不懂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中的代码来控制更改。