Skip to content

Commit

Permalink
Purge children from view registry when UIManager is invalidated (#38617)
Browse files Browse the repository at this point in the history
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: software-mansion/react-native-screens#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:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[IOS][FIXED] - Purge children from view registry on `RCTUIManager` invalidation.

Pull Request resolved: #38617

Test Plan:
You can run the [demo](https://github.com/mkondakov/RNSScreensMemoryLeak) provided in the [issue](software-mansion/react-native-screens#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
  • Loading branch information
kkafar authored and facebook-github-bot committed Oct 11, 2023
1 parent 3859eee commit bc63e44
Showing 1 changed file with 4 additions and 1 deletion.
5 changes: 4 additions & 1 deletion packages/react-native/React/Modules/RCTUIManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,11 @@ - (void)invalidate

RCTExecuteOnMainQueue(^{
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"UIManager invalidate", nil);
NSMutableDictionary<NSNumber *, id<RCTComponent>> *viewRegistry =
(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)self->_viewRegistry;
for (NSNumber *rootViewTag in self->_rootViewTags) {
UIView *rootView = self->_viewRegistry[rootViewTag];
id<RCTComponent> rootView = viewRegistry[rootViewTag];
[self _purgeChildren:[rootView reactSubviews] fromRegistry:viewRegistry];
if ([rootView conformsToProtocol:@protocol(RCTInvalidating)]) {
[(id<RCTInvalidating>)rootView invalidate];
}
Expand Down

0 comments on commit bc63e44

Please sign in to comment.