swift - 为什么在更改现有值的成员时会运行属性观察器?

标签 swift properties didset property-observer

请考虑这个 Swift 代码。我有一个包装另一个类实例的类。当我在保留值上设置属性时,包装类的属性观察器将运行。

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 在变量值改变时被调用。

因此,当“第 1 行”处的​​上述代码执行时,我了解到打印了“In MyWrapperClass didSet”,这很好。

接下来,当 Line2 执行时,我希望正确打印“In MyClass didSet”,但我不确定为什么打印“In MyWrapperClass didSet”,因为属性 myValue 未更改.谁能解释一下为什么?

最佳答案

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

  1. myValue 是协议(protocol)类型的属性(恰好也是可选的)。该协议(protocol)不是类绑定(bind)的,因此符合类型可以是值类型和引用类型。

  2. myStr 属性要求有一个隐式的 mutating setter 因为 (1) 和它没有被标记为 nonmutating。因此,协议(protocol)类型的值很可能在根据其 myStr 要求发生变化时发生变化。

考虑该协议(protocol)可能已被值类型采用:

struct S : MyProtocol {
  var msgStr: String?
}

在这种情况下,msgStr 的突变在语义上等同于将 S 值与 msgStr 的突变值重新分配回 myValue(参见 this Q&A for more info)。

或者可以将默认实现重新分配给 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 是类绑定(bind)的:

protocol MyProtocol : class {
  var msgStr: String? { get set }
}

或者如果 msgStr 要求指定 setter 必须是非可变的:

protocol MyProtocol {
  var msgStr: String? { get nonmutating set }
}

然后 Swift 会将 myValue.msgStr 的突变视为具有引用语义;也就是说,myValue 上的属性观察器不会被触发。

这是因为 Swift 知道属性值不能改变:

  1. 在第一种情况下,只有类可以符合,并且类的属性 setter 不能改变 self(因为这是对实例的不可变引用)。

  2. 在第二种情况下,msgStr 要求只能由类中的属性(并且此类属性不会改变引用)或类中的计算属性来满足setter 不可变的值类型(因此必须具有引用语义)。

或者,如果 myValue 刚刚被输入为 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

关于swift - 为什么在更改现有值的成员时会运行属性观察器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48842279/

相关文章:

ios - 使用 reachabilitySwift 检查互联网连接或 wifi 连接

ios - URLSession.shared.dataTask 卡住 UI

javascript - 为什么将变量作为属性添加到循环中的对象时,第一次会添加变量的实际名称作为属性?

swift - 在 Swift 中实现变量属性属性观察器

swift - UISwipeGestureRecognizer 导致抖动

ios - 从 Firebase 检索用户经纬度后添加注释

c# - 对象的成员是否应该在类中自动设置?

java - 我在第一次运行时创建的属性文件在第二次运行时被清空

swift - 在 Swift 中,重置 didSet 中的属性会触发另一个 didSet 吗?

swift - didSet 没有被 Swift 中的 Array.append() 调用