Skip to content

Commit 1f6b3cb

Browse files
t0maborokkafar
andauthored
fix(iOS, SplitView): Break the retain cycle between component and controller for SplitViewScreen (#3083)
## Description Closes: software-mansion/react-native-screens-labs#156 Problem: - Controller has a strong reference to an associated component via `.view` property. - In our case, Component keeps a strong reference to the controller. - This creates a retain cycle, which we're not breaking anywhere at the moment. Solution: SplitViewHost keeps references to all subviews of type SplitViewScreen all the time (columns might be hidden, but they won't disappear). It means that we can consider Host as an owner of the SplitViewScreens' lifecycles. If the host is about to be unmounted from the view hierarchy, we can perform some additional cleanup in child components to detach the reference to theirs controllers. ## Changes - Added a method for invalidating a screen component that removes a reference to the controller. ## Test code and steps to reproduce Create an example that includes a SplitView, which may be removed from the hierarchy, add log and observe. ## Checklist - [x] Included code example that can be used to test this change - [x] Ensured that CI passes --------- Co-authored-by: Kacper Kafara <[email protected]>
1 parent c6ac4a6 commit 1f6b3cb

File tree

3 files changed

+33
-0
lines changed

3 files changed

+33
-0
lines changed

ios/gamma/split-view/RNSSplitViewHostComponentView.mm

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ - (void)setupController
109109
}
110110
}
111111

112+
- (void)willMoveToWindow:(UIWindow *)newWindow
113+
{
114+
if (newWindow == nil) {
115+
[self invalidate];
116+
}
117+
}
118+
112119
- (void)didMoveToWindow
113120
{
114121
[self setupController];
@@ -134,6 +141,17 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller
134141
}
135142
}
136143

144+
- (void)invalidate
145+
{
146+
// We assume that split host is removed from view hierarchy **only** when
147+
// whole component is destroyed & therefore we do the necessary cleanup here.
148+
// If at some point that statement does not hold anymore, this cleanup
149+
// should be moved to a different place.
150+
for (RNSSplitViewScreenComponentView *subview in _reactSubviews) {
151+
[subview invalidate];
152+
}
153+
}
154+
137155
RNS_IGNORE_SUPER_CALL_BEGIN
138156
- (nonnull NSMutableArray<RNSSplitViewScreenComponentView *> *)reactSubviews
139157
{

ios/gamma/split-view/RNSSplitViewScreenComponentView.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ NS_ASSUME_NONNULL_BEGIN
2020
@property (nonatomic, strong, readonly, nonnull) RNSSplitViewScreenController *controller;
2121
@property (nonatomic, weak, readwrite, nullable) RNSSplitViewHostComponentView *splitViewHost;
2222

23+
/**
24+
* @brief A function responsible for requesting a cleanup in the SplitViewScreen component.
25+
*
26+
* Should be called when the component is about to be deleted.
27+
*/
28+
- (void)invalidate;
29+
2330
@end
2431

2532
#pragma mark - ShadowTreeState

ios/gamma/split-view/RNSSplitViewScreenComponentView.mm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ - (void)resetProps
8181
_columnType = RNSSplitViewScreenColumnTypeColumn;
8282
}
8383

84+
- (void)invalidate
85+
{
86+
// Controller keeps the strong reference to the component via the `.view` property.
87+
// Therefore, we need to enforce a proper cleanup, breaking the retain cycle,
88+
// when we want to destroy the component.
89+
_controller = nil;
90+
}
91+
8492
#pragma mark - ShadowTreeState
8593

8694
- (nonnull RNSSplitViewScreenShadowStateProxy *)shadowStateProxy

0 commit comments

Comments
 (0)