Andre
Andre

Reputation: 7648

iOS swift: Deint a child ViewController

I have an UITabBar inside a ViewController. Selecting tab bar items I add different child view controller. And remove the others.

But I never see the deint log on the console, only the viewWillDisappear..

How can I be sure to deint the child viewcontroller to save memory?

this is how I create the child controller:

private lazy var lotteryViewController: LotteryViewController = {
        // Load Storyboard
        let storyboard = UIStoryboard(name: "TrophyRoom", bundle: Bundle.main)

        // Instantiate View Controller
        var viewController = storyboard.instantiateViewController(withIdentifier: "LotteryViewController") as! LotteryViewController

        // Add View Controller as Child View Controller
        self.add(asChildViewController: viewController)

        return viewController
    }()

add child controller:

private func add(asChildViewController viewController: UIViewController) {
        // Add Child View Controller
        addChildViewController(viewController)

        // Add Child View as Subview
        containerView.addSubview(viewController.view)

        // Configure Child View
        viewController.view.frame = containerView.bounds
        viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        // Notify Child View Controller
        viewController.didMove(toParentViewController: self)
    }

remove method called from the father viewController:

private func remove(asChildViewController viewController: UIViewController) {
        // Notify Child View Controller
        viewController.willMove(toParentViewController: nil)

        // Remove Child View From Superview
        viewController.view.removeFromSuperview()

        // Notify Child View Controller
        viewController.removeFromParentViewController()
    }

Log methods of the child controllers:

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        print("Sessions View Controller Will Appear")
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        print("Sessions View Controller Will Disappear")
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
        // Log if a view controller is being deinited
        print("\nDeinit: \(self.description)\n")
    }

Upvotes: 1

Views: 1006

Answers (1)

matt
matt

Reputation: 535240

Class instance memory management works by keeping a count of retaining references. deinit means "this object is about to go out of existence because there are no more retaining references to it".

You are doing the parent-child "dance" correctly when you add a child view controller and when you remove a child view controller. So all is well from that standpoint. When you call viewController.removeFromParentViewController, it is certainly true that one retaining reference to it goes away, namely, the parent-child retaining reference (the childControllers array, which has removed and released the child).

But that does not mean that all retaining references go away. And indeed, the fact that deinit is not called signals that some other retaining reference exists. All you have to do now is find out what that reference is. But in fact it appears that you have shown us what it is:

private lazy var lotteryViewController: LotteryViewController = {
    let storyboard = UIStoryboard(name: "TrophyRoom", bundle: Bundle.main)
    var viewController = storyboard.instantiateViewController(withIdentifier: "LotteryViewController") as! LotteryViewController
    self.add(asChildViewController: viewController)
    return viewController
}()


lotteryViewController is a strong (retaining) reference to the child view controller. Thus, it keeps that view controller alive even after you've removed it as a child.

So there's a weakness in (or at least an unexpected consequence of) your architecture (by which I mean your curious use of a lazy var instance property with a define-and-call initializer). The way to make a child view controller is to instantiate the view controller, hand it over to the parent as its child, and let go of the view controller so that the only reference to it is the parent. (If you need a subsequent reference to the child, you can always get it by way of the parent.) You are not doing that, so you don't get the deinit when the child is removed from the parent.

Upvotes: 3

Related Questions