Calling a method requires a strong reference (like Danny shows above and John explained). Because self
is a strong reference inside the method, it remains valid up to the last (strong) use within the method. The weak case may fail to run the method, and the unowned case may crash simply because the reference is already invalid before the method call. Whether the ARC convention is +0 or +1 does not change the programming model, and may change between compiler releases. Of course, if you are relying on unspecified behavior, the ARC convention may affect that.
The language allows values to be destroyed any time after their last use (not including weak/unowned references). Beyond those constraints, lifetimes are implementation defined. As the optimizer improved (namely due to SIL-OSSA form), it became important for the compiler implementation to follow more conservative rules to avoid widespread source breaks at -O
. Since 5.7 (2022) the compiler introduced the concept of a deinitialization barrier within a variable's lexical scope. We refer to these rules under the potentially misleading name "lexical lifetimes":
FORUM: What is the current status of ‘lexical lifetimes’?
Shortening the lifetime of self
within a method was one of the optimizer behaviors that developers found most surprising. But we saw similar confusion with other local variables. Now the optimizer treats self
just like any local variable. If any weak or unsafe pointer access occurs within the variable's scope, and might refer to any object kept alive by the variable, then the optimizer conceptually extends the variable's lifetime to cover those accesses. Deinit barriers are irrelevant in OP's instanceMethod
because there are no weak or unsafe accesses inside the method.
These aren't formal language rules because we may decide to refine what constitutes a deinit barrier, and there are some outright exceptions:
- CoW types, which themselves aren't formalized, but cover
Array
,Set
,Dictionary
, andString
, have "optimized lifetimes" ~Copyable
values that may have a custom deinitializer have "strict lifetimes"~Escapable
values have "optimized lifetimes" (unless they are also~Copyable
+ custom deinit)
Optimized lifetimes give you no guarantee beyond the language's last use rule. This can trip up programmers who build an Array of weak references expecting the array to keep those object alive even though they never use the array.
Strict lifetimes treat the value's deinitialization as a proper side-effect (fully ordered), similar to other non-GC languages that have "synchronous deinitialization". This is consistent with non-Copyable being a way to opt-out of all the ARC baggage.
If you're aware that your code has unsafe/weak references and is relying on some Copyable local variable to keep them alive, then I recommend following the formal language rules and making that explicit with extendLifetime(variable)
. This includes extendLifetime(self)
if needed.