Search code examples
swiftautomatic-ref-countingweak-referencesdeinit

Deinit not called in special circumstance that involves NSHashTable


I came across something that's peculiar and interesting and would love to get inputs from anyone. So to start off with lets take this definition of the class:

class TestClass:NSObject {  
    var s1 = NSHashTable<TestClass>(options: .weakMemory)  

    func doit(bla:TestClass) {  
        s1.add(bla)  
        bla.s1.add(self)  
    }  

    deinit {  
        print("Deinit")  
    }  
}  

Now lets consider the following:

var t1:TestClass? = TestClass()  
var t2:TestClass? = TestClass()  

If we did the following deinit gets called:

t1?.s1.add(t2!)  
t2?.s1.add(t1!)  

t1 = nil // This will result in deinit being called  

Now lets do the same thing but by calling doit() method

t1?.doit(bla:t2!)  

t1 = nil // Deinit doesn't get called for some reason  

The question here is why isn't deinit being called in this situation? What is so different about this since it essentially has the same reference assignment as the first method?

I would love to get input from anyone on this.


Solution

  • As usual, the problem is that you are trying to test this in a playground. Don't. Playgrounds are the work of the devil.

    Test in an actual app project and you will see that deinit is called.

    Example (iOS, but the equivalent in macOS would do fine):

    import UIKit
    
    class TestClass:NSObject {
        var s1 = NSHashTable<TestClass>(options: .weakMemory)
        func doit(bla:TestClass) {
            s1.add(bla)
            bla.s1.add(self)
        }
        deinit {
            print("Deinit")
        }
    }
    
    class ViewController: UIViewController {
        var t1:TestClass? = TestClass()
        var t2:TestClass? = TestClass()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            t1?.doit(bla:t2!)
            t1 = nil // --> "Deinit"
            print(t2?.s1) // --> it's empty
        }
    }