From bc63e44b23baddf579d8e42b70af0473314f8e48 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Wed, 11 Oct 2023 07:31:57 -0700 Subject: [PATCH] Purge children from view registry when UIManager is invalidated (#38617) Summary: Talking about Paper & iOS here. In standard RN applications when a native component is removed permanently from view hierarchy [it is invalidated (if it implements `RCTInvalidating`)](https://github.com/facebook/react-native/blob/e64756ae5bb5c0607a4d97a134620fafcb132b3b/packages/react-native/React/Modules/RCTUIManager.m#L483-L495). Components that implement `RCTInvalidating` such as [`RNSScreenView`](https://github.com/software-mansion/react-native-screens/blob/9fb3bd00850bcdf29b46daa57e56eabda3ae30ea/ios/RNSScreen.mm#L35) of [`react-native-screens`](https://github.com/software-mansion/react-native-screens) library rely on `RCTInvalidating#invalidate` method being called in adequate moment to release retained resources (in my case the `RNSScreenView` holds a strong reference to it's view controller preventing it from being garbage collected). However in case of brownfield applications (React Native is used only for a particular view & loaded on demand, see: https://github.com/software-mansion/react-native-screens/issues/1754 for discussion & app example) when view controller holding `RCTRootView` is dismissed and whole `React Native` managed view / controller tree gets deallocated, `RCTInvalidating#invalidate` method is not called on the dismissed components, thus in my particular use case, leading to memory leak. Right now I've added call to `RCTUIManager#_purgeChildren:fromRegistry:` (which internally invalidates all components which implement `RCTInvalidating`) in `RCTUIManager#invalidate`. ## Changelog: [IOS][FIXED] - Purge children from view registry on `RCTUIManager` invalidation. Pull Request resolved: https://github.com/facebook/react-native/pull/38617 Test Plan: You can run the [demo](https://github.com/mkondakov/RNSScreensMemoryLeak) provided in the [issue](https://github.com/software-mansion/react-native-screens/issues/1754). Following screenshots show that memory leak in brownfield application is resolved. Without the change (`invalidate` method is not being called on native components) ![image](https://github.com/facebook/react-native/assets/50801299/dac331c2-1e7c-4e66-a8c1-b88f7a007d9b) With the change: ![image](https://github.com/facebook/react-native/assets/50801299/7a8afbe9-446c-47a2-a972-d7589b921677) Reviewed By: NickGerleman Differential Revision: D49952215 Pulled By: javache fbshipit-source-id: 6336b86774615acc40279c97e6ae0bb777bda8ad --- packages/react-native/React/Modules/RCTUIManager.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Modules/RCTUIManager.m b/packages/react-native/React/Modules/RCTUIManager.m index 9fe11430c8b7f0..c6b73cc5bbbdf0 100644 --- a/packages/react-native/React/Modules/RCTUIManager.m +++ b/packages/react-native/React/Modules/RCTUIManager.m @@ -101,8 +101,11 @@ - (void)invalidate RCTExecuteOnMainQueue(^{ RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"UIManager invalidate", nil); + NSMutableDictionary> *viewRegistry = + (NSMutableDictionary> *)self->_viewRegistry; for (NSNumber *rootViewTag in self->_rootViewTags) { - UIView *rootView = self->_viewRegistry[rootViewTag]; + id rootView = viewRegistry[rootViewTag]; + [self _purgeChildren:[rootView reactSubviews] fromRegistry:viewRegistry]; if ([rootView conformsToProtocol:@protocol(RCTInvalidating)]) { [(id)rootView invalidate]; }