为什么 属性 观察者 运行 当现有值的成员发生变化时?
Why does a property observer run when a member of the existing value is changed?
请考虑此 Swift 代码。我有一个 class,它包装了另一个 class 的实例。当我在保持值上设置 属性 时,包装器 class 的 属性 观察者是 运行.
protocol MyProtocol {
var msgStr: String? { get set }
}
class MyClass: MyProtocol {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}
class MyWrapperClass {
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
wrapperObj.myValue.msgStr = "Some other string" // Line2
以上代码的输出为:
In MyWrapperClass didSet
In MyClass didSet
In MyWrapperClass didSet
我知道didSet
是在变量值改变的时候调用的
所以当上面的代码在 "Line1" 执行时,我知道 "In MyWrapperClass didSet" 被打印出来了,这很好。
接下来,当 Line2 执行时,我希望正确打印 "In MyClass didSet",但我不确定为什么要打印 "In MyWrapperClass didSet",因为 属性 myValue
没有改变。有人可以解释为什么吗?
看起来像一个错误,请参阅:https://bugs.swift.org/browse/SR-239
另一种解决方法是预定义变量,例如:
protocol MyProtocol {
var msgStr: String? { get set }
}
class MyClass: MyProtocol {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}
class MyWrapperClass {
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
var obj = wrapperObj.myValue!
obj.msgStr = "Some other string" // Line2
我怀疑这是因为您的 protocol
未指定为 class
protocol
。因此,MyProtocol
可能是 struct
,因此当对象以任何方式更改时都会触发 didSet
(这是值类型的正确行为)。
如果您将 protocol
更改为:
protocol MyProtocol: class {
var msgStr: String? { get set }
}
则Swift知道MyProtocol
表示引用类型,所以didSet
在MyWrapperClass
中设置字符串时不会为myValue
调用.
Swift 需要将 myValue.msgStr
的变异视为具有值语义;这意味着需要触发 myValue
上的 属性 观察者。这是因为:
myValue
是一个 protocol-typed 属性 (也恰好是可选的)。此协议不是 class-bound,因此符合类型可以是值类型和引用类型。
myStr
属性 要求隐含地 mutating
setter 因为 (1) 和它还没有被满足的事实标记为 nonmutating
。因此,protocol-typed 值 很可能 通过其 myStr
要求进行变异。
考虑该协议可能已被值类型采用:
struct S : MyProtocol {
var msgStr: String?
}
在这种情况下,msgStr
的变异在语义上等同于 re-assigning 一个 S
值,msgStr
的变异值回到 myValue
(参见 )。
或者默认实现可以有 re-assigned 到 self
:
protocol MyProtocol {
init()
var msgStr: String? { get set }
}
extension MyProtocol {
var msgStr: String? {
get { return nil }
set { self = type(of: self).init() }
}
}
class MyClass : MyProtocol {
required init() {}
}
class MyWrapperClass {
// consider writing an initialiser rather than using an IUO as a workaround.
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
在这种情况下 myValue.myStr
re-assigns 一个 完全新的 实例突变为 myValue
.
如果 MyProtocol
是 class-bound:
protocol MyProtocol : class {
var msgStr: String? { get set }
}
或者如果 msgStr
要求指定 setter 必须是 non-mutating:
protocol MyProtocol {
var msgStr: String? { get nonmutating set }
}
然后 Swift 会将 myValue.msgStr
的变异视为具有引用语义;也就是说,myValue
上的 属性 观察者不会被触发。
这是因为Swift知道属性值不能改变:
第一种情况只有classes可以符合,属性setters on classes不能变异self
(因为这是对实例的不可变引用)。
在第二种情况下,msgStr
要求只能通过 class 中的 属性 来满足(并且此类属性不会改变reference)或通过值类型中的计算 属性,其中 setter 是 non-mutating(因此必须具有引用语义)。
或者,如果 myValue
刚刚被输入为 MyClass!
,你也会得到引用语义,因为 Swift 知道你正在处理一个 class:
class MyClass {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}
class MyWrapperClass {
var myValue: MyClass! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
wrapperObj.myValue.msgStr = "Some other string" // Line2
// In MyWrapperClass didSet
// In MyClass didSet
请考虑此 Swift 代码。我有一个 class,它包装了另一个 class 的实例。当我在保持值上设置 属性 时,包装器 class 的 属性 观察者是 运行.
protocol MyProtocol {
var msgStr: String? { get set }
}
class MyClass: MyProtocol {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}
class MyWrapperClass {
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
wrapperObj.myValue.msgStr = "Some other string" // Line2
以上代码的输出为:
In MyWrapperClass didSet
In MyClass didSet
In MyWrapperClass didSet
我知道didSet
是在变量值改变的时候调用的
所以当上面的代码在 "Line1" 执行时,我知道 "In MyWrapperClass didSet" 被打印出来了,这很好。
接下来,当 Line2 执行时,我希望正确打印 "In MyClass didSet",但我不确定为什么要打印 "In MyWrapperClass didSet",因为 属性 myValue
没有改变。有人可以解释为什么吗?
看起来像一个错误,请参阅:https://bugs.swift.org/browse/SR-239
另一种解决方法是预定义变量,例如:
protocol MyProtocol {
var msgStr: String? { get set }
}
class MyClass: MyProtocol {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}
class MyWrapperClass {
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
var obj = wrapperObj.myValue!
obj.msgStr = "Some other string" // Line2
我怀疑这是因为您的 protocol
未指定为 class
protocol
。因此,MyProtocol
可能是 struct
,因此当对象以任何方式更改时都会触发 didSet
(这是值类型的正确行为)。
如果您将 protocol
更改为:
protocol MyProtocol: class {
var msgStr: String? { get set }
}
则Swift知道MyProtocol
表示引用类型,所以didSet
在MyWrapperClass
中设置字符串时不会为myValue
调用.
Swift 需要将 myValue.msgStr
的变异视为具有值语义;这意味着需要触发 myValue
上的 属性 观察者。这是因为:
myValue
是一个 protocol-typed 属性 (也恰好是可选的)。此协议不是 class-bound,因此符合类型可以是值类型和引用类型。myStr
属性 要求隐含地mutating
setter 因为 (1) 和它还没有被满足的事实标记为nonmutating
。因此,protocol-typed 值 很可能 通过其myStr
要求进行变异。
考虑该协议可能已被值类型采用:
struct S : MyProtocol {
var msgStr: String?
}
在这种情况下,msgStr
的变异在语义上等同于 re-assigning 一个 S
值,msgStr
的变异值回到 myValue
(参见
或者默认实现可以有 re-assigned 到 self
:
protocol MyProtocol {
init()
var msgStr: String? { get set }
}
extension MyProtocol {
var msgStr: String? {
get { return nil }
set { self = type(of: self).init() }
}
}
class MyClass : MyProtocol {
required init() {}
}
class MyWrapperClass {
// consider writing an initialiser rather than using an IUO as a workaround.
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
在这种情况下 myValue.myStr
re-assigns 一个 完全新的 实例突变为 myValue
.
如果 MyProtocol
是 class-bound:
protocol MyProtocol : class {
var msgStr: String? { get set }
}
或者如果 msgStr
要求指定 setter 必须是 non-mutating:
protocol MyProtocol {
var msgStr: String? { get nonmutating set }
}
然后 Swift 会将 myValue.msgStr
的变异视为具有引用语义;也就是说,myValue
上的 属性 观察者不会被触发。
这是因为Swift知道属性值不能改变:
第一种情况只有classes可以符合,属性setters on classes不能变异
self
(因为这是对实例的不可变引用)。在第二种情况下,
msgStr
要求只能通过 class 中的 属性 来满足(并且此类属性不会改变reference)或通过值类型中的计算 属性,其中 setter 是 non-mutating(因此必须具有引用语义)。
或者,如果 myValue
刚刚被输入为 MyClass!
,你也会得到引用语义,因为 Swift 知道你正在处理一个 class:
class MyClass {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}
class MyWrapperClass {
var myValue: MyClass! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
wrapperObj.myValue.msgStr = "Some other string" // Line2
// In MyWrapperClass didSet
// In MyClass didSet