Search code examples
swiftclassoption-typeswift5deinit

Should an Instance of a Swift Class be assigned to `nil` to see the deinitializer in action?


Swift can have Deinitializers (Like C++ Destructors) for Classes. When I am using a Non-Optional Instance of a Class (That is, var obj: Class not var obj: Class?), I am unable to see the message printed by the Deinitializer. However, when an Optional Instance of a Class is assigned to nil, the Deinitializer message pops up. Even when a Non-Optional Instance of a Class is used, it would be automatically deallocated when the Reference Count gets over, right ? Then, Why is the deinitializer message not popping up for Non-Optional Instances ?

Example Code to reproduce this behavior:

class A: CustomStringConvertible
{
    var value: Int
    var description: String
    {
        get
        {
            "A (value = \(value))"
        }
    }
    
    init(_ value: Int)
    {
        self.value = value
    }
    
    deinit
    {
        print("\(self) is being deinitialized !")
    }
}

var a: A = A(5)
print(a)
var aOpt: A? = A(10)
print(aOpt!)
aOpt = nil

Output:

A (value = 5)
A (value = 10)
A (value = 10) is being deinitialized !

Solution

  • The deinitialization is managed via the swift ARC. To simplify: it is triggered for an object (not a variable) when there is no longer a valid reference to the object. This happens for example when setting an optional variable to nil. But also by assigning another object to a non-optional variable. And also when variables are no longer in scope (e.g. exiting a function when the object is not returned).

    The following adaptation of your code shows these different cases (see the comments for the details):

    class A
    {
        var value: Int
        init(_ value: Int)
        {
            self.value = value
            print("A object with ivalue \(value) was initialized")
        }
        deinit
        {
            print("A object with value \(value) was deinitialized")
        }
    }
    
    func test() {
        var a: A = A(5)       // (5) is initialized
        let b = A(6)          // (6) is initialized
        a = b                 // no reference anymore to (5) so it is deinitialized
        //a = nil             // not allowed because it's not optional
         
        var aOpt: A? = A(10)  // (10) in intialized
        aOpt = nil            // no reference anymore to (10) so it is deinitialized
    }                         // a dies, so no reference anymore to (6) which is deinitialized
    test()
    print ("This is the end")
    

    I managed to get all the objects to be deinitialized before reaching the final print, simply by declaring them in a function body. This gives a tighter control on the variable lifecycles. But when you declare global or static variables, it's different, because the variables continue to exist until termination of a programme. Would my snippet run the test() outside of the function, the object (6) would not be deinitialized, since it would still be still referenced by a variable.

    And with Swift, once the programme is terminated, it's over. Unlike C++, there is no guarantee that the remaining objects are properly deinitialized: Swift gives back the the resources to the the system without individually deinitializing every object.

    If this is a problem for you, the best is to avoid global variables as much as possible (this is in any case a good practice). And if you have to use them, make sure the the app has a single exit point that cleans the object properly.