Search code examples
memory-leaksuiviewcontrollerxctestuiwindow

How to destroy VC after adding to UIWindow?


For certain types of tests, I need the view controller to be inside a UIWindow. The UIWindow itself is never shown. I'm having trouble destroying the view controller.

Say we have

class MyViewController: UIViewController {
    deinit {
        print("MyVC bye-bye")
    }
}

class MyWindow: UIWindow {
    deinit {
        print("MyWindow bye-bye")
    }
}

Now in a test suite, I can make both objects and they'll be deallocated.

class MyTests: XCTestCase {
    func test() {
        let vc = MyViewController()
        let window = MyWindow()
    }
}

In the console output, I see

MyWindow bye-bye
MyVC bye-bye

But if I add the following line to the test, neither object is destroyed.

        window.addSubview(vc.view)

The window itself may be cleaned up later by the autorelease pool. But my main concern is that the view controller isn't destroyed.

I tried doing

        vc.view.removeFromSuperview()

but that's not enough.


Solution

  • Pumping the run loop gives the autorelease pool a kick, so that no objects linger after the test concludes. If the objects are purely local to the test, place the objects in the autorelease pool and do the pump:

    func test() {
        autoreleasepool {
            let vc = MyViewController()
            let window = MyWindow()
            window.addSubview(vc.view)
        }
        RunLoop.current.run(until: Date())
    }
    

    But in most test suites, the view controller is part of the test fixture. In that case, pump the loop after releasing the fixture.

    override func tearDown() {
        vc = nil
        RunLoop.current.run(until: Date())
        super.tearDown()
    }