deinit not called in specific case
Asked Answered
P

4

6

I have the following test case: I expect deinit to be called at program termination but it never is. I'm new to Swift but would not think this is expected behaviour. (this is not in a playground)

class Test
{
   init() {
      print( "init" )
   }

   deinit {
      print( "deinit" )
   }
}

print("Starting app")

var test = Test()

print( "Ending App" )

the output is:

Starting app
init
Ending App
Program ended with exit code: 0

If I place the code in a function and then call the function I get expected results

Starting app
init
Ending App
deinit
Program ended with exit code: 0

Shouldn't deinit of the object be called at program termination?

Paralogism answered 23/2, 2018 at 15:9 Comment(4)
As it's defined at the top-level, test is technically a global variable, therefore it never actually goes out of scope.Dorseydorsiferous
If put your "main code" inside a do { ... } block then deinit will be called.Maryland
@Dorseydorsiferous That makes no sense, it goes out of scope at program termination! (or at least logically it should). Easy enough to work around but it really makes no sense.Paralogism
@GregorBrandt You could argue that, but deinitialisers can't run after program termination :) Though that being said, the top-level main.swift scope is already pretty special (the global variables don't have lazy initialisers, you can throw uncaught errors, etc.), so it may be reasonable for deinitialisers to run just before program termination; feel free to file a bug if you want.Dorseydorsiferous
L
9

I expect deinit to be called at program termination

You should not expect that. Objects that exist at program termination are generally not deallocated. Memory cleanup is left to the operating system (which frees all of the program's memory). This is a long-existing optimization in Cocoa to speed up program termination.

deinit is intended only to release resources (such as freeing memory that is not under ARC). There is no equivalent of a C++ destructor in ObjC or Swift. (C++ and Objective-C++ objects are destroyed during program termination, since this is required by spec.)

Leotie answered 23/2, 2018 at 17:19 Comment(5)
Termination is one thing, but what about method return or throw; Is deinit called instantly when last obj-ref gets out of scope, or should I use again and again defer instead, where ever such behavior is needed?Donothing
@Donothing deinit may be called long before a variable goes out of scope if the object is deallocated early (extremely common and allowed by lifetime rules). deinit may also be called significantly after the last reference goes out of scope if there are additional autoreleases on it. (This is less common in pure Swift types, but is common when dealing with things that touch ObjC, which Swift often does behind the scenes.) If you want to execute something when a scope is exited, that is what defer is for, not deinit.Leotie
What if MySwiftClass inherits my Objective-C++ class; Would the Obj-C++ class's destructor get called instantly when last ref gets out of scope (by method return or throw)? If yes, should I implement func dispose() and call it form Obj-C++ class during destruct? (I mean, I don't think inheriting is enough, am I right?)Donothing
The object is still not promised to exist through its entire scope unless it's annotated with objc_precise_lifetime (you can't add that in Swift; it's only available in C). In Swift, you'd implement that using withExtendedLifetime to ensure that the object is not destroyed earlier than expected. The only change in a C++ destructor is that it will be called during exit(). The object may still have its lifetime extended if it has been autoreleased. The tool for what you're describing is defer, not any of the various deallocation methods.Leotie
If you're trying to recreate RAII in Swift, that isn't a pattern in Swift. Swift most commonly uses closures for that (for example, withUnsafeBytes makes Data's bytes storage available within the closure). Otherwise it uses defer. Swift offers tools like withExtendedLifetime available so that you can deal with things (mostly C++ things) that do rely on precise lifetimes. But Swift prefers to have explicit scopes using closures, rather than implicit scopes like RAII typically uses. (If you have a specific thing you're trying to solve, you should open a new question.)Leotie
M
1

If I place the code in a function and then call the function I get expected results

Yes, this works because the life of the test variable is defined by the scope of the method. When the scope of the method ends (i.e., method gets removed from the Stack), all local variables' life would end unless there are other strong references to those variables.

Shouldn't deinit of the object be called at program termination

There is no way to close an app gracefully in iOS programmatically like Android. The possible ways that an app can be closed are,

  1. By swiping up (terminating) from recent apps list
  2. Or making a deliberate crash.
  3. Or by letting the OS kill your app due to low-memory reasons.

All of your app's memory will be cleared up by the OS when it terminates, so the deinit will not be called, we cannot expect that too. You can note the word termination, which explains the fact that the program didn't end in a proper way, so we can't expect the OS to do the last honors.

Misdemeanant answered 4/3, 2018 at 12:7 Comment(0)
L
0

There is a similar question.

In Apple there are no clear explanation how the deinit works. Just it calls by ARC when the class is deallocating. I think when app terminates there is different mechanism for deiniting in contrast of regular runtime deiniting of the class. Or maybe the main window retain your class? Anyway, in order to perform code when app terminates you should use Application delegate (like applicationWillTerminate).

Lindseylindsley answered 23/2, 2018 at 15:31 Comment(0)
O
0

Deinit function only work when test object was deallocated, in first case should do like this to make sure test object was deallocated:

class Test {

init() {
    print( "init" )
}

deinit {
    print( "deinit" )
}
}

print("Starting app")
var test: Test? = Test()
test = nil
print("Ending App")

Then deinit will be called as you expect.

In second case, when your code push in a function, object will be dealloc when end of scope of function, so deinit in this case will be called

For more information, should visit there: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/deinitialization

Olympium answered 13/4, 2023 at 8:35 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.