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

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

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


当前回答

这些被称为属性观察员:

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

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

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

其他回答

Getter和setter有时太笨重,不能仅仅为了观察正确的值变化而实现。通常这需要额外的临时变量处理和额外的检查,如果你写了数百个getter和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观察器

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

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

您还可以使用didSet将变量设置为不同的值。这不会像属性指南中所述的那样导致再次调用观察者。例如,当你想限制值如下所示时,它是有用的:

let minValue = 1

var value = 1 {
    didSet {
        if value < minValue {
            value = minValue
        }
    }
}

value = -10 // value is minValue now.