Let me start this question off by stating that there are no upright desks left in my office. I've turned them all over. I need help understanding what is happening in Apple's UINavigationController
that is causing my weak reference to zero out.
First, before I explain the problem in any more detail, let me provide the code to reproduce.
First, create a subclass of UINavigationController
. This subclass will hold a weak reference to a UIViewController
subclass. It should be a reference to what will be passed in as the root view controller.
For evidence of my insanity, I've provided an initializer that reproduces the problem as well as an initializer that fixes my problem. For completeness, I've included the other initializers you need to make this compile.
class ChildNavController: UINavigationController {
weak var weakRoot: UIViewController?
init(withWeakFirst: Void) {
let root = UIViewController()
print("rootVC: \(root)")
weakRoot = root
super.init(rootViewController: root)
}
init(withWeakLast: Void) {
let root = UIViewController()
print("rootVC: \(root)")
super.init(rootViewController: root)
weakRoot = root
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}
The print
statements are strictly unnecessary, but I've included them because the default description of a UIViewController
includes the memory address.
Now... the code to test what is happening, which is fairly straight-forward...
First, testing my first initializer:
let weakFirst = ChildNavController(withWeakFirst: ())
if let _ = weakFirst.weakRoot {
print("weak first non-nil")
}
else {
print("weak first nil")
print("but the rootVC? \(weakFirst.viewControllers.first)")
}
The console output for this is:
rootVC: <UIViewController: 0x7f9de9f091a0>
weak first nil
but the rootVC? Optional(<UIViewController: 0x7f9de9f091a0>)
And comparing that to the second initializer, where I simply change the order between calling super.init and assigning into my weak property:
let strongFirst = ChildNavController(withWeakLast: ())
if let _ = strongFirst.weakRoot {
print("weak last non-nil")
}
else {
print("weak last nil")
print("but the rootVC? \(weakFirst.viewControllers.first)")
}
And the console output for this:
rootVC: <UIViewController: 0x7f9de9d11f70>
weak last non-nil
There are only three relevant lines of code here. The first line initializes a regular UIViewController
:
let root = UIViewController()
This is assigned into a local variable. The local variable goes away when the initializer returns, so if no external strong reference is created, the view controller should be deallocated.
Then I have two more lines.
weakRoot = root
and
super.init(rootViewController: root)
Somehow, the order of these two lines determines whether my weakRoot
property is nil
or is a reference to the same object contained in the 0th index of super.viewControllers
.
This does not make sense to me and I need help understanding what is going on.
Addendum: I have tried reproducing this problem with my own super classes. I've tried with Swift super classes and with Objective-C super classes. I can not reproduce the problem in those cases. I am only able to reproduce with UINavigationController
(however I do not know if there might be other Apple framework classes that replicate this behavior).