Answers:
重点似乎是,有时您需要一个具有自动存储和某些行为的属性,例如,通知其他对象该属性刚刚更改。当所有是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
代表了几行的经济性,并且字段列表中的噪音更少。
willSet
,didSet
不会调用和:willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
myArrayProperty.removeAtIndex(myIndex)
...不期望。
我的理解是set和get用于计算的属性(没有存储属性的支持)
如果您来自Objective-C,则请记住命名约定已更改。在Swift中,iVar或实例变量称为存储属性
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
}
}
}
请注意两个计算和存储属性的使用。我使用了计算属性来防止两次设置相同的值(以避免发生坏事!);我已经使用willSet和didSet将通知转发到viewControllers(请参阅UIViewController文档和有关viewController容器的信息)
希望对您有所帮助,如果我在这里的任何地方犯了错误,请大声喊叫!
//I can't see a way to 'stop' the value being set to the same controller - hence the computed property
我if let newViewController = _childVC {
代替 使用后警告消失 if (_childVC) {
get
,我认为您需要先添加if _childVC == nil { _childVC = something }
,然后再添加return _childVC
。
这些称为“ 属性观察者”:
财产观察员观察并响应财产价值的变化。每次设置属性值时都会调用属性观察器,即使新值与属性的当前值相同也是如此。
摘录自:苹果公司“ The Swift Programming Language”。iBooks。https://itun.es/ca/jEUH0.l
我怀疑这是允许我们传统上使用KVO做的事情,例如与UI元素的数据绑定,或触发更改属性,触发同步过程,后台处理等副作用。
注意
willSet
和didSet
代表团发生前,没有当属性在初始化设置所谓的观察家
现有的许多写得很好的答案都很好地涵盖了这个问题,但是我会更详细地提及我认为值得补充的内容。
在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.
}
之后,可以在某些视图控制器中使用您的委托函数,以观察for中模型的关键变化CustomViewController
,就像您将使用UITextFieldDelegate
for UITextField
对象的固有委托函数(例如textFieldDidEndEditing(...)
)一样。
对于此简单示例,请使用didSet
class属性的委托回调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()
有时,getter和setter太重了,以至于无法执行以观察正确的值更改。通常,这需要额外的临时变量处理和额外的检查,并且如果您编写数百个getter和setter的话,您甚至会希望避免那些琐碎的工作。这些东西适合这种情况。
willSet
didSet
我不懂C#,但我有点猜测,我想我明白了
foo : int {
get { return getFoo(); }
set { setFoo(newValue); }
}
做。它看起来与您在Swift中非常相似,但是并不相同:在Swift中您没有getFoo
and setFoo
。差别不小:这意味着您没有任何基础存储可用于其价值。
Swift已存储并计算了属性。
计算属性具有get
和可能具有set
(如果可写)。但是,如果getter和setter中的代码需要实际存储一些数据,则必须在其他代码中使用它属性中执行。没有后备存储。
另一方面,存储的属性确实具有后备存储。但它并没有具备get
和set
。相反,它有willSet
和didSet
你可以用它来观察变量的变化,并最终触发的副作用和/或修改存储的值。您没有willSet
和didSet
对于计算的属性,也不需要它们,因为对于计算的属性,可以使用代码set
来控制更改。
getFoo
并且setFoo
是简单的占位符,可用于您希望使用getter和setter进行的任何操作。C#也不需要它们。(在访问编译器之前,我确实想念了一些语法上的微妙之处。)
get
&set
)基本上应具有基于另一个属性计算的属性,例如,将标签text
转换为yearInt
。didSet
&willSet
会说...嘿,这个值已经设置好了,现在让我们开始吧,例如我们的dataSource已更新...所以让我们重新加载tableView使其包含新行。有关另一个示例,请参见dfri关于如何在didSet