willSet和setSet在Swift中的用途是什么?
Swift有一个非常类似于C#的属性声明语法:
var foo: Int { get { return getFoo() } set { setFoo(newValue) } }
但是,它也有willSet
和didSet
动作。 这些在调用者被调用之前和之后被分别调用。 考虑到你可以在setter中使用相同的代码,他们的目的是什么?
这一点似乎是,有时候,你需要一个拥有自动存储和一些行为的属性,例如通知其他对象该属性刚刚改变。 当你所有的东西都被get
/ set
,你需要另一个字段来保存这个值。 使用willSet
和didSet
,当值被修改而不需要另外的字段时,可以采取行动。 例如,在这个例子中:
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 } } }
所以willSet
和didSet
代表了几行的经济,并且在字段列表中噪音较小。
我的理解是,设置和获取计算属性 (没有从存储的属性的支持)
如果您是来自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元素绑定数据,或者触发改变属性的副作用,触发同步过程,后台处理等等。
注意
在委托发生之前,在初始化程序中设置属性时,不会调用
willSet
和didSet
观察程序
您也可以使用didSet
将variables设置为不同的值。 这不会导致观察者按照属性指南中的说明再次被调用。 例如,如果您想限制该值,如下所示:
let minValue = 1 var value = 1 { didSet { if value < minValue { value = minValue } } } value = -10 // value is minValue now.
现有的很多答案都很好地涵盖了这个问题,但是我会详细地提到一个我认为值得讨论的问题。
可以使用willSet
和didSet
属性观察器来调用委托,例如,用于仅由用户交互更新的类属性,但是您希望避免在对象初始化时调用委托的位置。
我会引用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
的模型中的关键变化,就像使用UITextFieldDelegate
的UITextField
对象的固有委托函数(例如textFieldDidEndEditing(...)
) 。
对于这个简单的示例,使用class属性value
的didSet
中的委托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,你甚至会想要避免这些小小的劳动。 这些东西是为了这个情况。
在你自己的(基类)类中, willSet
和didSet
是相当复杂的 ,因为你可以定义一个计算属性(即get-和set-方法)来访问_propertyVariable
,并进行所需的前后处理 。
但是 ,如果您重写了已经定义属性的类, 那么 willSet
和didSet
是有用的,而不是多余的!
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中,你没有getFoo
和setFoo
。 这并没有什么不同:这意味着您没有任何底层存储来满足您的价值。
Swift已经存储并计算了属性。
计算属性已经get
并可能已经set
(如果它是可写的)。 但是getter和setter中的代码,如果需要实际存储一些数据,则必须在其他属性中进行。 没有后备存储。
另一方面,存储的属性具有备份存储。 但它没有get
和set
。 相反,它具有willSet
和didSet
,您可以使用它来观察variables的变化,并最终触发副作用和/或修改存储的值。 您没有willSet
和didSet
计算的属性,并且您不需要它们,因为对于计算的属性,您可以使用set
的代码来控制更改。