As I recall, the docs say that objects held weakly "may be released at any time." The operative part is "may be".
I'm guessing that your view controller and navigation controller are auto-released. This is a term that harks back to the days of manual reference counting, but is still relevant under the covers. The object is created with a retain count of 1, and then added to an "auto-release pool". Then, next time your app's current function returns and it visits the event loop, the "auto-release pool is drained", which means that every entry in the auto-release pool gets a release message, dropping it's retain count by 1. When the retain count drops to zero, the object is deallocated.
(ARC actually uses reference counting under the covers, and so retain counts and auto-release pools are still relevant. It's just that with ARC the compiler takes care of maintaining them for you. Your strong and weak references get turned into to calls to the low level system retain, release, and auto-release functions.)
I'm not sure if it would work in the context of a test, but you might be able to use code like this:
func testReferences() throws {
var strongVC: UIViewController? = UIViewController()
var strongNC: UINavigationController? = UINavigationController(rootViewController: strongVC!)
weak var weakVC = strongVC
weak var weakNC = strongNC
strongVC = nil
XCTAssertNotNil(weakVC)
XCTAssertNotNil(weakNC)
strongNC = nil
DispatchQueue.main.async {
XCTAssertNil(weakVC) // fails
XCTAssertNil(weakNC) // fails
}
}
}
That code would cause the calls to XCTAssertNil()
to be deferred until the next pass through the event loop.
The problem with that code is that by the time the call to DispatchQueue.main.async()
is executed, the test may be over.
Edit:
As pointed out by Cristik in their comment, the better way would be to use an autoreleasepool command:
func testReferences() throws {
//Define the vars we want to test outside of the auto-release pool statement.
weak var weakVC: UIViewController
weak var weakNC: UINavigationController
autoreleasepool {
var strongVC: UIViewController? = UIViewController()
var strongNC: UINavigationController? = UINavigationController(rootViewController: strongVC!)
weakVC = strongVC
weakNC = strongNC
strongVC = nil
XCTAssertNotNil(weakVC)
XCTAssertNotNil(weakNC)
strongNC = nil
}
//Test for nil outside of the autorelasepool statement,
//after the auto-release pool is drained.
XCTAssertNil(weakVC) // fails
XCTAssertNil(weakNC) // fails
}