UITab memory leak

I have the following view hierarchy in my app: [UINavigationController] -> [MainViewController] -> [MyTabBarController] -> [DashboardViewController]

In my MainViewController I have a button that pushes the MyTabBarController onto the navigation controllers stack. In the tab bar controller I only have one tab in this example showing the DashboardViewController.

That all works fine, and when I tap the back button on MyTabBarController, everything works fine and the MainViewController is shown again. The UI works exactly how I want it, but when I load up the 'Debug Memory Graph' view, I can see that my DashboardViewController is still in memory and it seems the UITab has a reference to it. The MyTabBarController is NOT in memory anymore.

MyTabBarController is very simple:

class MyTabBarController: UITabBarController {

  override func viewDidLoad() {
    super.viewDidLoad()
    
    self.mode = .tabSidebar
    
    var allTabs:[UITab] = []
    
    let mainTab = UITab(title: "Dashboard",
                        image: UIImage(systemName: "chart.pie"),
                   identifier: "dashboard",
       viewControllerProvider: { _ in
                                 return UINavigationController(rootViewController: DashboardViewController())
                               })
    allTabs.append(mainTab)

    setTabs(allTabs, animated: false)
  }
}

And the DashboardViewController is empty:

class DashboardViewController: UIViewController {
}

The only reason I created as a seperate class in this example is so I can easily see if it's visible in the memory debug view.

I have uploaded the simple sample app to GitHub: https://github.com/fwaddle/TabbarMemoryLeakCheck

Anyone have any suggestions?

Here is a screen grab of the memory debug view showing the UITab having a reference to the DashboardViewController even though MyTabBarController has been dealloc'd:

Hello Moff,

Thank you for the focused Xcode project and screenshot. Unfortunately, I am not yet able to replicate this issue. Could you please provide which version of Xcode you are using and which iOS version and device of the simulator you are running on?

Thank you for your patience,

Richard Yeh  Developer Technical Support

Hi Richard, Thanks for the quick response. I am on Xcode 26.2, and running iPadOS 26.2. Interestingly enough, it works fine on an iPhone simulator, but not the iPad.

I've just run into this issue and I can confirm that the memory leak

  1. does not occur on iPhone (iOS 18.7.1 and iOS 26) even when using UITabs
  2. occurs on iPad (iOS 18.7.1) only when using UITabs. It does not occur when setting the tab bar controller children the old way (setViewControllers(:animated:)).

A work around is to use a custom TabBarController inheriting from UITabBarController and override viewDidDisappear(:) as follows:

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        // HACK: clear the tabs property to prevent memory leaks when using UITabs
        if #available(iOS 18, *) {
            setTabs([], animated: false)
        }
    }

It effectively triggers the children view controllers deinit.

App build with Xcode 26.2 on macOS 26.2.

Hello Moff,

Thanks for the investigation into this issue. This appears to be something for further investigation by the appropriate engineering team. Can you please create a bug report via Feedback Assistant that includes your sample application?

Once you open the bug report, please post the FB number here for my reference. If you have any questions about filing a bug report, take a look at Bug Reporting: How and Why?

Thanks for your due diligence,

Richard Yeh  Developer Technical Support

Hi Richard,

I have submitted the feedback: FB21885154

Thanks, Mof.

Hi Patatrouf, I tried this and it didn't work for me. The problem in my memory graph is (as far as I can see), is that the UITab is being kept in memory NOT by the tab bar, but by a UIFloatingTabBarItemView which seems to be kept in a _subViewCache. So setting the update UITabs had no affect for me since its not the tab bar that is holding the ref. Perhaps what you're seeing is a slight variation on what is happening here? Glad your little hack worked for you, but it doesn't work for me. I have spent a couple of days trying to work around this one without much success. The only thing I can do is force dealloc all my own objects within the ViewControllers that sit inside the UITabs. This doesn't stop the ViewControllers staying in memory but at least they're using minimal memory at this stage. My code is polluted with a whole heap of extra code now, but it is what it is.

Moff, I confirm that on my side the unreleased reference also comes from _UIFloatingTabBarItemView.

Richard, as to why my hack didn't work for Moff, it is definitely related to the version of iPadOS.

  • iPadOS 18.7.1 (physical iPad): there is an additional unreleased reference to the tab bar view controller from a _UIFloatingTabBarItemCell. However my hack works.
  • iPadOS 26.2 (simulator): there's only one reference from a _UIFloatingTabBarItemView, exactly as you described (same memory graph as yours). And indeed my hack doesn't work

I hope the issue will be fixed for both versions of iPadOS. Please keep us posted here.

Hello patatrouf,

Thank you for all of the additional investigation. If you'd like to be automatically notified via Feedback Assistant, you can always file a bug report and I'll make sure it issues you updates accordingly.

Hello Moff,

Thanks for filing your bug report! I've made sure to route it to the right team.

Glad to be working together,

Richard Yeh  Developer Technical Support

UITab memory leak
 
 
Q