In my app I have a RootViewController which all my ViewControllers are subclassed from. When developing, I usually use this in my RootVC:

deinit { 
    print("\(type(of: self)) deinit")

so that I always can see when any of my viewControllers deinit. It prints:

MyExampleViewController deinit

Today I noticed that one of them didn't deinit when I navigated away from it. Let's call it DetailViewController. It's a completely normal (Root)ViewController-subclass pushed into the main NavigationController. When hitting the Back button in the navigation, it navigates away, but never says it deinits. This is the first pushed controller, so I can't pop the controller before to see if that helps. But any controller pushed after the DetailViewController gets deinited fine when navigating back and forth.

I decided to check the memory graph, so I ran my app again, pushed to the DetailViewController, then popped it away by clicking the Back button in the navigation, then I clicked Debug memory graph.

In the debug navigator on the left, I scroll down and see that there exists one instance of my DetailViewController. If I push back and forth several times before opening the memory graph, there are as many different instances of this DetailViewController as times I've pushed and popped.

When clicking it, I see this:

Memory Graph The DetailViewController is the single controller on the far right. I haven't used Memory Graph that much, but I assume that the "solid" white lines are strong claims, and that the slightly more transparent (gray) lines are weak claims. Meaning that there's one strong claim to my controller. The one on the bottom.

This is the bottom row: Memory Graph 2

What does this mean? It seems like my (custom) NavigationController has an array called _childViewControllers which retains my popped controller. To clarify, I don't have any stored variables in my custom NavigationController. It's only subclassed to override 5 functions, that is all. I have about 20 different ViewControllers being pushed and popped by this exact same custom `NavigationController, but they all have no problem with this.

Am I reading the graph wrong? There has to be a different strong claim that's not visible in the graph, right? When I "pop" the viewController by clicking Back, shouldn't my viewController be removed by _childViewControllers?

  • 1
    It seems like a retain cycle, I think that the issue is on "DetailViewController" and not on your navigation controller? Do you have any timer, async callback, completion handler or something that can cause a retain cycle? – LorenzOliveto Apr 10 at 14:07
  • Interesting.. yes, I do have quite a lot of that. Is there a way to narrow down where the issue can be? I guess I'm missing a [weak self] somewhere, if this is the case. But shouldn't I be able to spot this in the memory graph somehow? I only see the objects holding a reference to my Detail, but not any references that my Detail holds on. Is there a way to see this, without going manually into all the hundreds of objects in the list on the left and look for it? – Sti Apr 10 at 14:12
  • Unfortunately I don't think that this is visible in the memory graph, I've done a small app with a timer that retains a view controller but I don't see the cycle in the memory graph. I have not worked a lot with this tool so take it with a grain of salt. – LorenzOliveto Apr 10 at 14:40
  • 1
    For debugging, you may need to begin commenting out suspect code until the problem goes away, then gradually add the suspect code back in until you find the offending lines. – Mike Taverne Apr 11 at 6:46
  • @MikeTaverne That's a good idea, but this is a huge and old ViewController, and I have no idea when this started (it might've been like this since the beginning), and it has a huge hierarchy within itself, so that will take a lot of time. Was hoping there was a way to debug it instantly.. I'll dig a bit deeper into the graph before I attempt that. – Sti Apr 11 at 8:11

Figured it out at last. Unfortunately, I had to go through commenting out several hundred lines of code bit by bit until I found out when it started to deinit when expected. The issue was a missing [weak self] in a closure, not unexpected, but it was in a completely different class, connected through a complicated hierarchy.

  • 1
    Memory leaks are the worst, but always worth finding. – Mike Taverne Apr 11 at 15:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.