Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift XCTest: Verify proper deallocation of weak variables

Recently I was attempting to verify that an object I wrote properly deallocates using a unit test. I found however that no matter what I tried the object would not deallocate before the test completed. So I reduced the test to a trivial example (seen below) that attempts to prove the basics of object deallocation using weak variables.

In my mind, the strong reference should stop retaining the object after the test method exits, and the weak reference should be nil when referenced on the next run loop. However, the weak reference is never nil and both tests fail. Am I misunderstanding something here? Below are the unit tests in full.

class Mock { //class type, should behave with reference semantics

    init() { }
}

class DeallocationTests: XCTestCase {   

    func testWeakVarDeallocation() {   

        let strongMock = Mock()

        weak var weakMock: Mock? = strongMock

        let expt = expectation(description: "deallocated")

        DispatchQueue.main.async {

            XCTAssertNil(weakMock)      //This assertion fails

            expt.fulfill()
        }

        waitForExpectations(timeout: 1.0, handler: nil)
    }

    func testCaptureListDeallocation() {   

        let strongMock = Mock()

        let expt = expectation(description: "deallocated")

        DispatchQueue.main.async { [weak weakMock = strongMock] in

            XCTAssertNil(weakMock)      //This assertion also fails

            expt.fulfill()
        }

        waitForExpectations(timeout: 1.0, handler: nil)
    }
}

I thought that maybe deallocation was being deferred somehow by XCTest, but even wrapping the test method bodies in an autoreleasepool did not cause the object to deallocate.

like image 755
Patrick Goley Avatar asked Sep 02 '25 04:09

Patrick Goley


1 Answers

The problem is that your testWeakVarDeallocation() function hasn't exited when the dispatchAsync block is called so a strong reference to strongMock is still held.

Try it like this (allowing testWeakVarDeallocation() to exit) and you'll see weakMock becomes nil as expected:

class weakTestTests: XCTestCase {
    var strongMock: Mock? = Mock()

    func testWeakVarDeallocation() {
        weak var weakMock = strongMock

        print("weakMock is \(weakMock)")

        let expt = self.expectation(description: "deallocated")

        strongMock = nil

        print("weakMock is now \(weakMock)")

        DispatchQueue.main.async {
            XCTAssertNil(weakMock)      // This assertion fails

            print("fulfilling expectation")
            expt.fulfill()
        }

        print("waiting for expectation")
        self.waitForExpectations(timeout: 1.0, handler: nil)
        print("expectation fulfilled")
    }
}
like image 76
par Avatar answered Sep 05 '25 01:09

par