UINavigationController's interactivePopGestureRecognizer sometimes causes the popped view controller to never be released (memory leak)

I notice if I pop a view controller off a UINavigationController's navigation stack using the interactivePopGestureRecognizer, every so often the view controller being popped off is not being released from memory.

Haven't been able to come up with a workaround or determine anything that seems to make the issue reproduce reliably. I simply log out dealloc in the view controller that is going to be popped. Push and pop a few times and notice that dealloc doesn't log out after it is being popped off.

Not sure if something in the UI (other animation) that is interrupting the pop gesture's _UINavigationInteractiveTransition ...

I haven't been able to track down what is retaining my view controller. When the issue happens I can see it is still in memory (I have it listen to UISceneWillEnterForegroundNotification and I put the scene in the background then move it to the foreground and hit a breakpoint).

But...I can't seem to figure out what is strongly retaining the view controller. From he debugger I start setting view controller properties to nil to see if I can break a potential retain cycle I created) but no luck. I'm only able to reproduce the memory leak when I dismiss the view controller with the interactive pop gesture (not the back button). I know that's not a lot to go on but am wondering if someone has experience anything similar and has a potential workaround?

You can use Xcode memory graph debugger to see what is holding a strong reference to the view controller, after reproducing the issue. Could you please reproduce the issue and look at what the memory graph debugger says is retaining the view controller?

Accepted Answer

Thanks a lot for your response. That was very helpful (not sure why I didn't think of using the memory graph debugger duh)! The leak is caused by UISwipeActionsConfiguration. The view controller that is being leaked has a UITableView and implements the delegate method:

-(nullable UISwipeActionsConfiguration*)tableView:(UITableView*)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath*)indexPath

So the leak occurs when I start the navigation controller's interactive pop gesture and the table view simultaneously recognizes the gesture and calls the -tableView:leadingSwipeActionsConfigurationForRowAtIndexPath: method and I return a UISwipeActionsConfiguration just before the view controller is popped off the stack via the interactive pop gesture recognizer...

The private UISwipeActionController is still holding on to the UISwipeActionsConfiguration, which is holding on to my view controller even after it has been dismissed from the UI, thus causing the leak. Ouch!

Just to add (for anyone else out there interested), reproducing the issue is a bit tricky. You have to invoke the interactive pop gesture a certain way to get the table view delegate method to be called, and then just pop it off in one motion. For me I set a log in

-(nullable UISwipeActionsConfiguration*)tableView:(UITableView*)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath*)indexPath
{
    NSLog(@"-leadingSwipeActionsConfigurationForRowAtIndexPath called");
   
   UISwipeActionsConfiguration *theConfig;
  //make the object and return it..
   return theConfig;
}

I also set a log in dealloc to verify whether or not the view controller is released.

So I have to play with it a few times, push and pop the view controller. But when I see the log in -leadingSwipeActionsConfigurationForRowAtIndexPath: and I still have my finger down (and the interactive pop gesture is still recognizing my touch) and I pop off the view controller, my log in dealloc does not show up and the view controller leaks.

I carefully went through each UIContextualAction I pass to the UISwipeActionsConfiguration and do a __weak and __strong dance like this:

__weak MyViewController *weakSelf = self;
   UIContextualAction *myAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal

                                                                                         title:actionTitle

                                                                                       handler:^(UIContextualAction *action,

                                                                                                 UIView *sourceView,

                                                                                                 void (^completionHandler)(BOOL))

 {
    __strong MyViewController *strongSelf = weakSelf;
    //do whatever to respond to the action using strongSelf.
}];

I believe this has now allowed my view controller to be released when I reproduce the issue, since the UIContextualActions don't retain the view controller in their action handlers. I think the UISwipeActionsConfiguration is still leaking though....with no obvious way for me to clear it when the view controller gets popped off the navigation stack (UISwipeActionController is private API). But at least the leak is not as bad.

Instruments leak tool never picked up this leak I only found it by unexpectedly hitting breakpoints on a view controller that wasn't supposed to be in memory.

Thanks for looking at this with the memory debugger! With block-based APIs, you are responsible for breaking the retain cycle (with strongSelf and weakSelf).

Are you able to confirm, in the memory debugger itself, whether or not the UISwipeActionsConfiguration is leaking?

UINavigationController's interactivePopGestureRecognizer sometimes causes the popped view controller to never be released (memory leak)
 
 
Q