【问题标题】:Is it possible to allow didSet to be called during initialization in Swift?是否可以允许在 Swift 初始化期间调用 didSet?
【发布时间】:2014-10-03 13:03:06
【问题描述】:

问题

Apple's docs 指定:

第一次初始化属性时不会调用 willSet 和 didSet 观察者。仅当属性的值在初始化上下文之外设置时才会调用它们。

是否可以在初始化期间强制调用这些?

为什么?

假设我有这门课

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

我创建了方法doStuff,以使处理调用更简洁,但我宁愿只处理didSet 函数中的属性。有没有办法在初始化期间强制它调用?

更新

我决定只删除我的类的便利初始化程序,并强制您在初始化后设置属性。这让我知道didSet 将始终被调用。我还没有决定这是否总体上更好,但它很适合我的情况。

【问题讨论】:

  • 老实说,最好只是“接受这就是 swift 的工作方式”。如果您在 var 语句上放置一个内联初始化值,那当然不会调用“didSet”。这就是为什么“init()”情况也不能调用“didSet”。这一切都说得通。
  • @Logan 这个问题本身回答了我的问题;)谢谢!
  • 如果你想使用便捷初始化器,可以和defer结合使用:convenience init(someProperty: AnyObject) { self.init() defer { self.someProperty = someProperty }

标签: ios swift didset


【解决方案1】:

如果您在 initializer 中使用defer,用于更新任何可选属性或进一步更新您已经初始化并且在调用任何super.init() 方法之后的非可选属性,那么你的willSet、didSet等就会被调用。我发现这比实现必须跟踪在正确位置调用的单独方法更方便。

例如:

public class MyNewType: NSObject {

    public var myRequiredField:Int

    public var myOptionalField:Float? {
        willSet {
            if let newValue = newValue {
                print("I'm going to change to \(newValue)")
            }
        }
        didSet {
            if let myOptionalField = self.myOptionalField {
                print("Now I'm \(myOptionalField)")
            }
        }
    }

    override public init() {
        self.myRequiredField = 1

        super.init()

        // Non-defered
        self.myOptionalField = 6.28

        // Defered
        defer {
            self.myOptionalField = 3.14
        }
    }
}

将产生:

I'm going to change to 3.14
Now I'm 3.14

【讨论】:

  • 厚脸皮的小把戏,我喜欢它……不过 Swift 的怪癖。
  • 太棒了。您发现了另一个使用 defer 的有用案例。谢谢。
  • 很好地使用了延迟!希望我能给你一个以上的支持。
  • 非常有趣.. 我以前从未见过这样使用 defer。它雄辩而有效。
  • 但是您将 myOptionalField 设置了两次,这从性能角度来看并不是很好。如果进行繁重的计算怎么办?
【解决方案2】:

创建一个自己的 set-Method 并在您的 init-Method 中使用它:

class SomeClass {
    var someProperty: AnyObject! {
        didSet {
            //do some Stuff
        }
    }

    init(someProperty: AnyObject) {
        setSomeProperty(someProperty)
    }

    func setSomeProperty(newValue:AnyObject) {
        self.someProperty = newValue
    }
}

通过将someProperty 声明为类型:AnyObject!(隐式 未包装的可选),您允许 self 完全初始化而无需 someProperty 正在设置中。你打电话的时候 setSomeProperty(someProperty) 你正在调用相当于 self.setSomeProperty(someProperty)。通常你将无法 这样做是因为 self 尚未完全初始化。自从 someProperty 不需要初始化,您正在调用 方法依赖于自身,Swift 离开初始化上下文并 didSet 将运行。

【讨论】:

  • 为什么这与初始化中的 self.someProperty = newValue 不同?你有一个有效的测试用例吗?
  • 不知道它为什么有效——但它确实有效。在 init() 方法中直接设置新值不会调用 didSet() —— 但使用给定的方法 setSomeProperty() 会。
  • 我想我知道这里发生了什么。通过将someProperty 声明为类型:AnyObject!(隐式展开的可选),您允许self 在不设置someProperty 的情况下完全初始化。当您调用setSomeProperty(someProperty) 时,您调用的是self.setSomeProperty(someProperty) 的等价物。通常你不能这样做,因为self 还没有完全初始化。由于someProperty 不需要初始化并且您正在调用依赖于self 的方法,因此Swift 会离开初始化上下文并且didSet 将运行。
  • 看起来任何在实际 init() 之外设置的东西都会被调用 didSet。从字面上看,任何未设置 init() { *HERE* } 的东西都会调用 didSet。非常适合这个问题,但使用辅助函数设置一些值会导致调用 didSet。如果您想重用该 setter 函数,那就不好了。
  • @Mark 说真的,试图将常识或逻辑应用于 Swift 是荒谬的。但是您可以相信赞成票或自己尝试。我刚刚做了,它有效。是的,这根本没有意义。
【解决方案3】:

作为 Oliver 答案的变体,您可以将这些行包装在一个闭包中。例如:

class Classy {

    var foo: Int! { didSet { doStuff() } }

    init( foo: Int ) {
        // closure invokes didSet
        ({ self.foo = foo })()
    }

}

编辑:Brian Westphal 的回答更好恕我直言。关于他的好处是它暗示了意图。

【讨论】:

  • 这很聪明,不会用多余的方法污染类。
  • 很好的答案,谢谢!值得注意的是,在 Swift 2.2 中,您需要始终将闭包包裹在括号中。
  • @Max Cheers,样本现在包括括号
  • 聪明的答案!请注意:如果您的类是其他类的子类,则需要在 ({ self.foo = foo })() 之前调用 super.init()
  • @Hlung,我不觉得它比其他任何东西都更有可能在未来中断,但仍然存在一个问题,即如果不注释代码,它的作用不是很清楚。至少 Brian 的“延迟”回答给了读者一个提示,我们要在初始化之后执行代码。
【解决方案4】:

我遇到了同样的问题,这对我有用

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        defer { self.someProperty = someProperty }
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

【讨论】:

  • 你从哪里得到的?
  • 这种方法不再有效。编译器会抛出两个错误: – 在所有存储属性初始化之前,在方法调用“$defer”中使用了“self” – 从初始化程序返回而不初始化所有存储属性
  • 你是对的,但有一个解决方法。您只需要将 someProperty 声明为隐式展开或可选,并为其提供一个带有 nil 合并的默认值以避免失败。 tldr; someProperty 必须是可选类型
【解决方案5】:

如果您在子类中执行此操作,则此方法有效

class Base {

  var someProperty: AnyObject {
    didSet {
      doStuff()
    }
  }

  required init() {
    someProperty = "hello"
  }

  func doStuff() {
    print(someProperty)
  }
}

class SomeClass: Base {

  required init() {
    super.init()

    someProperty = "hello"
  }
}

let a = Base()
let b = SomeClass()

在a 示例中,didSet 未被触发。但在b 示例中,didSet 被触发,因为它在子类中。它必须与 initialization context 的真正含义有关,在这种情况下,superclass 确实关心这一点

【讨论】:

  • 嗨@onmyway133 swift 是一种类型安全的语言,它总是完全初始化一个类型,否则。当你初始化 someClass.基类被完全初始化,然后在 SomeClass 中设置 someProperty = "hello"。因此 didSet 被调用
【解决方案6】:

虽然这不是一个解决方案,但另一种解决方法是使用类构造函数:

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            // do stuff
        }
    }

    class func createInstance(someProperty: AnyObject) -> SomeClass {
        let instance = SomeClass() 
        instance.someProperty = someProperty
        return instance
    }  
}

【讨论】:

  • 此解决方案将要求您更改 someProperty 的可选性,因为它不会在初始化期间为其赋值。
【解决方案7】:

在特定情况下,如果您想在 init 内为超类中可用的属性调用 willSet 或 didSet,您可以直接分配您的超属性:

override init(frame: CGRect) {
    super.init(frame: frame)
    // this will call `willSet` and `didSet`
    someProperty = super.someProperty
}

请注意,带有闭包的Charlesism 解决方案在这种情况下也始终有效。所以我的解决方案只是一个替代方案。

【讨论】:

    【解决方案8】:

    不幸的是,在初始化根类时不会调用 didSet 观察者。

    如果你的类不是子类,你必须使用 getter 和 setter 来实现你想要的功能:

    class SomeClass {
        private var _test: Int = 0
        var test: Int {
            get { _test }
            set { _test = newValue }
        }
        
        init(test: Int) { self.test = test }
    }
    

    或者,如果你的类是子类,你可以使用 didSet 和 do:

    override init(test: int) {
        super.init()
        self.test = test
    }
    

    应该在调用 super.init() 之后调用 didSet。

    一件事我没有尝试过,但也可能有用:

    init(test: int) {
        defer { self.test = test }
    }
    

    注意:您需要使您的属性可以为空,或者为它们设置一个默认值,或者解开类属性。

    【讨论】:

      【解决方案9】:

      你可以用obj-с方式解决:

      class SomeClass {
          private var _someProperty: AnyObject!
          var someProperty: AnyObject{
              get{
                  return _someProperty
              }
              set{
                  _someProperty = newValue
                  doStuff()
              }
          }
          init(someProperty: AnyObject) {
              self.someProperty = someProperty
              doStuff()
          }
      
          func doStuff() {
              // do stuff now that someProperty is set
          }
      }
      

      【讨论】:

      • 嗨@yshilov,我认为这并不能真正解决我希望从技术上解决的问题,当您调用self.someProperty 时,您将离开初始化范围。我相信同样的事情可以通过var someProperty: AnyObject! = nil { didSet { ... } } 来完成。不过,有些人可能会喜欢它作为一种解决方法,感谢您的补充
      • @Logan 不,那行不通。在 init() 中 didSet 将被完全忽略。
      猜你喜欢
      • 1970-01-01
      • 2018-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-30
      • 1970-01-01
      相关资源
      最近更新 更多