Swift 需要将 的突变myValue.msgStr
视为具有值语义;这意味着myValue
需要触发属性观察器。这是因为:
myValue
是一个协议类型的属性(也恰好是可选的)。该协议不是类绑定的,因此符合类型可以是值类型和引用类型。
由于(1) 和未标记的事实,myStr
属性要求具有隐式设置器。因此,协议类型的值很可能会根据其要求在变异时发生变异。mutating
nonmutating
myStr
考虑该协议可能已被值类型采用:
struct S : MyProtocol {
var msgStr: String?
}
在这种情况下, 的突变在msgStr
语义上等同于重新分配S
具有突变值的值msgStr
back to myValue
(有关更多信息,请参阅此 Q&A)。
或者默认实现可能已重新分配给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
重新分配给。myValue
如果MyProtocol
是类绑定:
protocol MyProtocol : class {
var msgStr: String? { get set }
}
或者如果msgStr
要求已指定 setter 必须是非变异的:
protocol MyProtocol {
var msgStr: String? { get nonmutating set }
}
那么 Swift 会将 的突变myValue.msgStr
视为具有引用语义;也就是说,myValue
不会触发属性观察器。
这是因为 Swift 知道属性值不能改变:
在第一种情况下,只有类可以符合,并且类上的属性设置器不能变异self
(因为这是对实例的不可变引用)。
在第二种情况下,该msgStr
要求只能由类中的属性(并且此类属性不会改变引用)或值类型中的计算属性满足,其中 setter 是非可变的(因此必须具有参考语义)。
或者,如果myValue
刚刚键入 as MyClass!
,您还将获得引用语义,因为 Swift 知道您正在处理一个类:
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