iOS 15 More Tab Crash

Hello,

The app I'm working on crashes by trying to reference a deallocated object. The object is a subclass of UITabBarController, and this crash only happens when there are enough view controllers in the tab bar that the more tab becomes available.

When turning on zombies, this is the message that gets printed: *** -[UITabBarControllerSubClass removeChildViewController:]: message sent to deallocated instance 0x7fefa98b2a00

In order for the crash to happen, the user has to perform an action that re-initializes the tab bar, such as logging out. This crash does not happen in iOS 14.

Does anyone know what might be happening here?

Post not yet marked as solved Up vote post of aotondo Down vote post of aotondo
3.1k views
Add a Comment

Replies

Hi aotondo,

we are experiencing the same issue as you described in our app. Did you find any fixes or workarounds? Thanks.

  • I was able to figure out that this has to do with changing the view controllers of a navigation controller within an animation block. If you set them directly, then the crash will happen, but if you use other APIs like push or dismiss, then it doesn't crash.

  • Actually, ignore that last part. My current fix is that you need to call layoutIfNeeded on the navigation controller's view after you set the view controllers.

  • In our case the app was crashing when the user is logging out. To show login screen we need to replace the rootViewController of the current UIWindow and the logout is happening from a view controller that is located inside more view controllers on tab bar. The deallocation of that tab bar controller after replacing rootViewController causes the issues and my temporary fix was to programatically select the tab bar item that is not in the more view controllers and to retain the tab bar for another 2s. I will also try your solution to see if it helps in my case. Thanks!

I can reproduce this one if I hold a strong reference to the UITabBarController subclass before I push it onto the navigation stack. The reference gets overwritten the next time I do it, the controller gets deallocated and I get the crash (with zombies enabled). There's no real reason for this to be strong, so I changed it to weak and it seems to resolve the problem.

This seems to be a timing issue and releasing the reference earlier resolves it in the simplest state. It's possible you have a reference somewhere else (perhaps in a block that's delaying the dealloc and causing your problem).

  • I've come across another very basic scenario that causes a crash here:

    Setup a split view controller that creates and shows a new tab bar controller (with enough tabs to show the more control) as the detail view controller every time a row is selected in the master list. When using an iPhone, The first time a row is selected, the tab bar controller shows as expected. After dismissing the detail and selecting a row for the second time, the app crashes with this error.

    What's interesting here is that there's no crash on the iPad. There's no animation in the iPad version which agrees with the earlier comment regarding animated changes.

Add a Comment

Did anyone find some other solutions? I tried all and nothing works, fixed also some memory leaks i had. In my project it happens randomly, sometimes the first time i do logout, sometimes i need to repeat the process 10-20 times in a row. I made also a minimal demo project with a nav controller that pushes the tab bar controller and at logout it pops to root, like the setup of my real app, but i can't make it crash.

After weeks of struggling i decided to get rid of the native More screen and made one myself. It was needing anyway some customizations that were a pain to do the native way.

I solved this by explicitly removing the child view controllers before deallocating the UITabBarController:

window.rootViewController?.children.forEach { $0.removeFromParent() }
window.rootViewController = MyNewRootView()

The issue appears to be with how the UIMoreListController improperly retains the child view controllers until after UITabBarController finishes its dealloc, and then when those child view controllers go to dealloc they crash on a now-invalid (unowned?) reference to the UITabBarController as their parent view controller.

This only seems to occur when the UIMoreNavigationController is set as the UITabBarController's _selectedViewController ivar. Unfortunately, it's not easy to detect if that's the case, because the getter for selectedViewController will (contrary to what is said in the header comments) return whichever of the UITabBarController's viewControllers children is shown in the UIMoreNavigationController, NOT the UIMoreNavigationController reference directly.

I was able to workaround this issue by always running

tabBarController.selectedViewController = tabBarController.viewControllers.firstObject;

to switch back to the first tab's (i.e. not under "More") controller immediately before triggering the deallocation of the UITabBarController. (In my case, the UITabBarController is already offscreen at this point, so there's no visual artifact from this last-moment switching.)