willSet和setSet在Swift中的用途是什么?

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

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

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

这一点似乎是,有时候,你需要一个拥有自动存储一些行为的属性,例如通知其他对象该属性刚刚改变。 当你所有的东西都被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代表了几行的经济,并且在字段列表中噪音较小。

我的理解是,设置和获取计算属性 (没有从存储的属性的支持)

如果您是来自Objective-C,请牢记命名惯例已经改变。 在Swift中,一个iVar或实例variables被命名为stored属性

示例1(只读属性) – 带有警告:

 var test : Int { get { return test } } 

这将导致一个警告,因为这会导致一个recursion函数调用(getter自己调用)。在这种情况下,警告是“试图修改'test'在自己的getter中”。

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

类似的问题 – 你不能这样做,因为它recursion调用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实际上是一个实例variables

例4.使用will和did设置

 //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容器的信息)

我希望这有助于,如果我在这里任何地方犯了一个错误,请有人喊!

这些被称为财产观察员

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

摘录自:苹果公司“Swift编程语言”,iBooks。 https://itun.es/ca/jEUH0.l

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

注意

在委托发生之前,在初始化程序中设置属性时,不会调用willSetdidSet观察程序

您也可以使用didSet将variables设置为不同的值。 这不会导致观察者按照属性指南中的说明再次被调用。 例如,如果您想限制该值,如下所示:

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

现有的很多答案都很好地涵盖了这个问题,但是我会详细地提到一个我认为值得讨论的问题。


可以使用willSetdidSet属性观察器来调用委托,例如,用于仅由用户交互更新的类属性,但是您希望避免在对象初始化时调用委托的位置。

我会引用Klaas对接受的答案的评论:

当一个属性第一次初始化时,willSet和didSet观察者不会被调用。 只有当属性值设置在初始化上下文之外时,才会调用它们。

这是一个相当整洁,因为它意味着,例如,对于您自己的自定义类, didSet属性是代表callback函数和启动函数的启动点的很好select。

作为一个例子,考虑一些自定义的用户控件对象,用一些关键的属性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 ... // Eg 'value = 1' would not call didSet at this point } // ... some methods/actions associated with your user control. } 

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

对于这个简单的示例,使用class属性valuedidSet中的委托callbackvalue来告诉视图控制器其中一个sockets已经关联了模型更新:

 // 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属性,或者你会以无限的recursion结束。

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

并注意, willSet需要一个参数名称来解决,另一方面, didSet不需要。

在更新属性的值后调用didSet观察者。 它比较旧的价值。 如果总步数增加,则会打印一条消息以指示已经采取了多less新步骤。 didSet观察者不会为旧值提供自定义参数名称,而是使用默认名称oldValue。

Getter和setter有时过于沉重,无法实现正确的值更改。 通常这需要额外的临时variables处理和额外的检查,如果你编写了数百个getter和setter,你甚至会想要避免这些小小的劳动。 这些东西是为了这个情况。

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

但是 ,如果您重写了已经定义属性的类, 那么 willSetdidSet有用的,而不是多余的!

didSet真正方便的一件事是当你使用sockets来添加额外的configuration。

 @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 ,您可以使用它来观察variables的变化,并最终触发副作用和/或修改存储的值。 您没有willSetdidSet计算的属性,并且您不需要它们,因为对于计算的属性,您可以使用set的代码来控制更改。