为什么 属性 观察者 运行 当现有值的成员发生变化时?

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表示引用类型,所以didSetMyWrapperClass中设置字符串时不会为myValue调用.

Swift 需要将 myValue.msgStr 的变异视为具有值语义;这意味着需要触发 myValue 上的 属性 观察者。这是因为:

  1. myValue 是一个 protocol-typed 属性 (也恰好是可选的)。此协议不是 class-bound,因此符合类型可以是值类型和引用类型。

  2. 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知道属性值不能改变:

  1. 第一种情况只有classes可以符合,属性setters on classes不能变异self(因为这是对实例的不可变引用)。

  2. 在第二种情况下,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