Swift class de-initialized at end of scope instead of after last usage

纵饮孤独 提交于 2019-12-31 06:22:43

问题


I've asked this question asking about the guarantees of the lifetime of a reference in a local variable and was referred to this thread in which the exact semantics of the lifetime of references to class instances were discussed. AFAICT from that thread, a class instance may be de-initialized right after the last usage of a variable containing the last reference, even before other expressions in the same statement are evaluated. It is argued there that allowing this behavior is necessary to prevent creating unnecessary copies in copy-on-write data structures, which I can follow.

This brought to my attention that up to now I've never seen Swift behave in that manner. Whenever I traced the execution, whether in a debug or release build, class instances were only initialised when leaving a complete scope.

In the following example in main(), I create an instance of a class which prints to the console during its lifecycle. Note that the instance is not used during or after the call to print() in main():

public final class Something {
    init() { print("Something.init()") }
    deinit { print("Something.deinit") }
}

func main() {
    let something = Something()
    print("in main()")
}

main()

When building and running this example with the debug or release configuration, I see the following order of execution, i.e. the instance is kept alive during the call to print() in main():

$ swift run -c release
Something.init()
in main()
Something.deinit

Instead, I would have expected the following order of execution:

$ swift run -c release
Something.init()
Something.deinit
in main()

I am using the Swift compiler 4.0.3 distributed with Xcode 9.2:

$ swift --version
Apple Swift version 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)
Target: x86_64-apple-macosx10.9

How can this order of execution be explained, considering that the developers of the Swift compiler are trying to be very agressive with decrementing ARC reference counters (and this de-initializing class instances) to avoid unnecessary copies in copy-on-write data structures? Is there some other optimization at play here because in this case keeping the reference alive does not actually lead to unnecessary work (just memory being allocated for longer than strictly necessary)?

My question is not about solving a particular issue I have, after all the compiler is creating code that is well withing the bounds of what the language allows. I would like to get a good understanding of how the Swift compiler is treating references and what optimizations are at play so that I can gauge which code patterns lead to good performance in critical situations, e.g. when large data structures would need to be copied by copy-on-write or when a large number of references are involed which need to be incremented and decremented.


回答1:


Let's analize how Swift works is respect to memory management:

  1. an object is deallocated when no are no more strong references to it
  2. a variable is "destroyed" when the scope where it's declared ends

Given the above two above facts, your Something instance is kept alive by the something variable which holds a strong reference to it until the end of scope, i.e. until the main function returns.

Try replacing let something = Something() by _ = Something() and you see that the expected behaviour that you want:

Something.init()
Something.deinit
in main()

Same behaviour occurs when holding a weak reference to the instance: weak var something = Something()

Note that there is a slight difference here between the something variable and the Something instance. The variable holds a reference to the instance, not the instance itself. Thus, the reference lifetime is the same as the variable lifetime, and this is what affect the instance's lifetime. Why the variable is not destroyed sooner, e.g. when the compiler detects it's no longer needed, I don't know.




回答2:


If you had made your variable an optional and set it to nil, you would have explicitly told the compiler where you want the reference to be released.

e.g.

func main() {
    var something:Something? = Something()
    something = nil
    print("in main()")
}

Otherwise you are letting the compiler decide execution order on its own. In the same way that it could decide to execute any line of code out of order, it can also decide when to deallocate local variables.

In your example "something" is an non-optional local variable, so it is likely that the compiler will decide to allocate it on the stack. This will mean that the variable's value (on the stack) will hold the reference until the function returns.



来源:https://stackoverflow.com/questions/48986455/swift-class-de-initialized-at-end-of-scope-instead-of-after-last-usage

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!