Swift有一个非常类似于C#的属性声明语法:
var foo: Int { get { return getFoo() } set { setFoo(newValue) } }
但是,它也有willSet
和didSet
行动.这些在分别调用setter之前和之后调用.考虑到你可以在setter中使用相同的代码,它们的目的是什么?
我的理解是set和get是针对计算属性的(没有来自存储属性的支持)
如果你是来自Objective-C,请记住命名约定已经改变.在Swift中,iVar或实例变量被命名为stored属性
var test : Int { get { return test } }
这将导致警告,因为这会导致递归函数调用(getter调用自身).在这种情况下的警告是"尝试在其自己的getter中修改'test'".
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.此外,请注意,此代码不会抱怨没有初始化者,因为没有存储属性可以初始化.
这是一种允许条件设置实际存储属性的模式
//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实际上是一个实例变量
//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拦截了实际存储属性的变化.这对于发送通知,同步等非常有用...(参见下面的示例)
//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元素的数据绑定,或触发更改属性的副作用,触发同步过程,后台处理等等.
注意
willSet
和didSet
代表团发生前,没有当属性在初始化设置所谓的观察家
关键在于,有时候,您需要一个具有自动存储和某些行为的属性,例如通知其他对象该属性刚刚更改.当你拥有的是get
/时set
,你需要另一个字段来保存值.使用willSet
和didSet
,您可以在修改值时执行操作,而无需其他字段.例如,在该示例中:
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
代表了几行的经济,并且在现场列表中的噪音更少.
您还可以使用将didSet
变量设置为其他值.这不会导致按照属性指南中的说明再次调用观察者.例如,当您想要将值限制如下时,它非常有用:
let minValue = 1 var value = 1 { didSet { if value < minValue { value = minValue } } } value = -10 // value is minValue now.
许多写得很好的现有答案很好地涵盖了这个问题,但我会详细地提到一个我认为值得报道的补充.
在willSet
和didSet
财产观察者可以用来打电话的代表,例如,对于通过用户交互永远只能更新类的属性,但要避免在调用对象的初始化委托.
我会引用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
,就像您使用UITextFieldDelegate
for 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
属性,否则最终会无限递归.customUserControl
didChangeValue()