Swift的属性声明语法与c#非常相似:
var foo: Int {
get { return getFoo() }
set { setFoo(newValue) }
}
但是,它也有willSet和didSet动作。它们分别在调用setter之前和之后调用。考虑到在setter中可以有相同的代码,它们的目的是什么?
Swift的属性声明语法与c#非常相似:
var foo: Int {
get { return getFoo() }
set { setFoo(newValue) }
}
但是,它也有willSet和didSet动作。它们分别在调用setter之前和之后调用。考虑到在setter中可以有相同的代码,它们的目的是什么?
当前回答
现有的许多精心编写的答案很好地涵盖了这个问题,但我将详细地提到一个我认为值得讨论的补充。
willSet和didSet属性观察者可以用来调用委托,例如,对于只由用户交互更新的类属性,但是你想避免在对象初始化时调用委托。
我将引用克拉斯对已接受答案的向上投票评论:
当属性是第一个时,不会调用willSet和didSet观察器 初始化。它们只在设置属性值时被调用 在初始化上下文之外。
这是非常简洁的,因为这意味着例如,didSet属性是委托回调和函数的启动点,对于你自己的自定义类来说是一个很好的选择。
例如,考虑一些自定义用户控件对象,带有一些关键属性值(例如,在评级控件中的位置),实现为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的固有委托函数为UITextField对象(例如textFieldDidEndEditing(…))。
对于这个简单的例子,使用来自类属性值的didSet的委托回调来告诉视图控制器它的一个outlet已经有了相关的模型更新:
// 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属性,否则你将以无限递归结束。
其他回答
在你自己的(基)类中,willSet和didSet是相当多余的,因为你可以定义一个计算属性(即get-和set-方法)来访问_propertyVariable并进行所需的前后处理。
然而,如果你重写一个已经定义了属性的类,那么willSet和didSet是有用的,而不是多余的!
重点似乎是,有时您需要一个具有自动存储和某些行为的属性,例如通知其他对象该属性刚刚更改。当您只有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代表了两行代码的节省,并且字段列表中的噪音更少。
didSet非常方便的一件事是当您使用outlet添加额外配置时。
@IBOutlet weak var loginOrSignupButton: UIButton! {
didSet {
let title = NSLocalizedString("signup_required_button")
loginOrSignupButton.setTitle(title, for: .normal)
loginOrSignupButton.setTitle(title, for: .highlighted)
}
Getter和setter有时太笨重,不能仅仅为了观察正确的值变化而实现。通常这需要额外的临时变量处理和额外的检查,如果你写了数百个getter和setter,你会想要避免这些微小的劳动。这些东西是为这种情况准备的。
每当属性被赋值时,属性的willSet和didSet观察者。即使新值与当前值相同,也是如此。
注意,willSet需要一个参数名来处理,另一方面,didSet不需要。
在property的值更新后调用didSet观察者。它与旧值进行比较。如果总步数增加了,则打印一条消息以指示已经执行了多少新步数。didSet观察者没有为旧值提供自定义参数名,而是使用默认名称oldValue。