我已经让this question询问了局部变量中引用生命周期的保证,并且引用了this thread,其中讨论了类实例引用生命周期的确切语义。来自该线程的AFAICT,即使在评估同一语句中的其他表达式之前,也可以在最后一次使用包含最后一个引用的变量之后立即取消初始化类实例。有人认为允许这种行为是必要的,以防止在写时复制数据结构中创建不必要的副本,我可以遵循。
这引起了我的注意,到目前为止,我从未见过斯威夫特以这种方式行事。每当我跟踪执行时,无论是在调试版本还是发布版本中,类实例仅在离开完整范围时初始化。
在main()
的以下示例中,我创建了一个类的实例,该类在其生命周期中打印到控制台。请注意,在print()
中调用main()
期间或之后不使用该实例:
public final class Something {
init() { print("Something.init()") }
deinit { print("Something.deinit") }
}
func main() {
let something = Something()
print("in main()")
}
main()
当使用调试或发布配置构建和运行此示例时,我看到以下执行顺序,即在print()
中调用main()
期间实例保持活动状态:
$ swift run -c release
Something.init()
in main()
Something.deinit
相反,我本来期望以下执行顺序:
$ swift run -c release
Something.init()
Something.deinit
in main()
我正在使用随Xcode 9.2一起发布的Swift编译器4.0.3:
$ swift --version
Apple Swift version 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)
Target: x86_64-apple-macosx10.9
考虑到Swift编译器的开发人员试图通过减少ARC引用计数器(以及这种去初始化类实例)以避免写入时复制数据结构中不必要的副本,如何解释这个执行顺序?是否还有一些其他优化在这里发挥作用,因为在这种情况下保持引用存活实际上不会导致不必要的工作(只是内存被分配的时间超过严格必要)?
我的问题不是解决我遇到的特定问题,毕竟编译器创建的代码很好地符合语言所允许的范围。我想很好地理解Swift编译器如何处理引用以及正在进行哪些优化,以便我可以判断哪些代码模式在关键情况下会产生良好的性能,例如:当需要通过写时复制或大量引用需要递增和递减的引用来复制大型数据结构时。
让我们分析Zwift如何工作以尊重内存管理:
鉴于上述两个事实,你的Something
实例由something
变量保持活着,该变量持有强引用,直到范围结束,即直到main
函数返回。
尝试用let something = Something()
替换_ = Something()
,你会看到你想要的预期行为:
Something.init()
Something.deinit
in main()
持有对实例的弱引用时会发生相同的行为:weak var something = Something()
请注意,something
变量和Something
实例之间存在细微差别。该变量包含对实例的引用,而不是实例本身。因此,参考生命周期与变量生命周期相同,这就是影响实例生命周期的因素。为什么变量不会更快被破坏,例如当编译器检测到它不再需要时,我不知道。
如果您已将变量设置为可选并将其设置为nil,那么您将明确地告诉编译器您希望释放引用的位置。
EG
func main() {
var something:Something? = Something()
something = nil
print("in main()")
}
否则,您将让编译器自行决定执行顺序。就像它可以决定不按顺序执行任何代码行一样,它也可以决定何时解除分配局部变量。
在您的示例中,“something”是一个非可选的局部变量,因此编译器可能会决定在堆栈上分配它。这意味着变量的值(在堆栈上)将保持引用,直到函数返回。