【问题标题】:Defer statement at the end of deinit produces a warningdeinit 末尾的 Defer 语句会产生警告
【发布时间】:2019-08-18 13:42:06
【问题描述】:

由于 Xcode 10.2 (Swift 5)deinit 范围末尾的 defer 语句产生:

作用域结束前的'defer'语句总是立即执行;替换为“do”语句以消除此警告

我们来看看这个例子:

var foo: String {
    didSet {
        // smt
    }
}

deinit {
    defer { <--- Warning
        foo = bar
    }
}
  • 当然可以通过将代码从观察者移动到方法并显式调用它来消除此警告,但是……

此警告的意义何在? - 在 deinit 中包含 defer 语句是否合理? (例如,能够触发属性的观察者).

【问题讨论】:

  • 如果您按照警告的建议执行操作并将defer { } 替换为do { },会发生什么情况?
  • 如果将 foo = bar 放在 deinit 的末尾而不使用 defer 会发生什么?
  • 在这两种情况下,属性的观察者都不会被执行。建议的do 本身是多余的,观察者不会在initdeinit 中运行。

标签: swift deinit property-observer


【解决方案1】:

警告是正确的,因为这里使用defer 不会改变程序的执行顺序,而这正是该语句的设计目的。然而不幸的是,建议的替换会改变程序的行为(提交了一个错误:SR-10207)。

值得注意的是,使用defer 来触发属性观察器有点小技巧,只有在类型检查器认为它是与deinit 主体不同的上下文时才会起作用。您也可以使用闭包表达式实现相同的结果:

  deinit {
    { foo = bar }()
  }

理想情况下,应该有某种形式的语法让您告诉 Swift“不要在此处执行直接存储访问”,这样就不需要这种变通方法,但目前还没有。

一个不那么棘手的解决方法是将反初始化器的所需逻辑提取到一个单独的方法中,该方法将逻辑置于属性访问正常完成的上下文中:

class C {
  var bar = ""

  var foo: String {
    didSet {
      // smt
    }
  }

  init(foo: String) { self.foo = foo }

  private func doDeinit() {
    foo = bar
  }

  deinit {
    doDeinit()
  }
}

【讨论】:

  • 谢谢,我想知道这是否是某种defer 声明滥用,但我想不是。 defer 语句将执行推迟到退出当前范围,所以对我来说 - 它在执行时不在范围内。建议的替换确实很不幸。这似乎是一个缺陷。
  • @JakubTruhlář 不完全——from the Swift language guide: "defer 语句用于在将程序控制转移到 defer 语句出现的范围之外之前执行代码 "
  • 好点子,你会认为它在init/deinit 中触发观察者是一个错误吗?我猜文档中 defer 部分的公式已经改变 - 基于打开的错误SR-1437
  • @JakubTruhlář 我不会真的认为这是一个错误,因为现在改变行为会破坏源代码,因为相当多的人依赖使用 defer 来触发属性观察器,但它并没有不太符合“造成积极伤害”的标准,以保证改变 IMO。不幸的是,我们仍然坚持这种行为。
  • 感谢您提交错误报告SR-10207@Hamish。
猜你喜欢
  • 2021-12-14
  • 2011-06-02
  • 2022-12-12
  • 2012-12-16
  • 1970-01-01
  • 2019-04-18
  • 1970-01-01
  • 2011-01-16
相关资源
最近更新 更多