Swift 和 RAII:项目和 playground 之间的 deinit 行为不一致

Swift and RAII: deinit behavior inconsistent between project and playground

让我先声明我已经通读了 this other question and the documentation for ARC 中的所有内容。

我正在尝试使用 RAII 习惯用法来处理问题,但 运行 遇到一些不一致的地方,导致它无法正常工作。

这是一个证明不一致的例子:

class TestClass {
    init() {
        print("init")
    }
    deinit {
        print("deinit")
    }
}

func test() {
    TestClass()
    defer { print("defer") }
    print("end of scope")
}

test()

根据我的 C++ 经验,我希望 TestClass 实例的 deinit 发生在 test 函数范围的末尾,这就是我想要的行为做。

同时我意识到我正在初始化 TestClass 的一个实例,但没有将它存储在一个变量中,所以没有对它的强引用,因此它是有意义的立即解除分配。

所以问题和问题就出现了,因为这种行为在项目和游乐场之间是不一致的。

在操场上,上面的代码产生以下输出:

init
end of scope
defer
deinit // deinit happens at end of scope
after scope

但是在一个项目中,完全相同的代码会产生不同的结果:

init
deinit // deinit happens immediately
end of scope
defer
after scope

所以

  1. 为什么项目和 playground 之间的行为不同?
  2. 有没有办法control/guarantee项目中将使用哪种行为?如果可能的话,我想要操场行为。

为了在边栏和结果预览中显示对象,Playgrounds 对对象进行了各种引用。

您应该将 playground 的强引用视为完全不确定的。对于任何实际的生命周期调试,您需要 运行 将您的代码放在独立的程序、库等中。

Playgrounds 的编译方式不同——与常规编译不同,编译器通过 inserting various calls to instrumentation 转换您的代码。例如,这使您可以查看表达式的计算结果。然而,它可能会带来不幸的副作用,即改变程序的行为方式。

但值得注意的是,与 C 和 C++ 等语言不同,Swift 不保证局部变量(或者在您的情况下,未使用的表达式的值)在范围结束之前保持有效他们被定义。优化器可以自由地提前取消初始化它们。

如果你想在你的例子中保证 SomeClass 的生命周期,你可以使用 withExtendedLifetime:

func test() {
  withExtendedLifetime(TestClass()) {
    defer { print("defer") }
    print("end of scope")
  }
}