@@ -70,6 +70,10 @@ @implementation RNSTabBarController {
7070
7171 RNSTabsNavigationState *_Nullable _pendingOperation;
7272
73+ #if RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE
74+ BOOL _didInstallPushInterceptor;
75+ #endif // RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE
76+
7377#if !RCT_NEW_ARCH_ENABLED
7478 BOOL _isControllerFlushBlockScheduled;
7579#endif // !RCT_NEW_ARCH_ENABLED
@@ -102,6 +106,19 @@ - (instancetype)initWithTabsHostComponentView:(nullable RNSTabsHostComponentView
102106 return self;
103107}
104108
109+ - (void )dealloc
110+ {
111+ #if RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE
112+ // Clear the OBJC_ASSOCIATION_ASSIGN back-reference to self stored on moreNavigationController.
113+ // This is a safety measure — moreNavigationController should never outlive us, but if it ever does
114+ // (e.g. due to UIKit lifecycle changes), we avoid leaving a dangling pointer behind.
115+ if (_didInstallPushInterceptor) {
116+ objc_setAssociatedObject (
117+ self.moreNavigationController , kRNSTabBarControllerAssociationKey , nil , OBJC_ASSOCIATION_ASSIGN );
118+ }
119+ #endif // RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE
120+ }
121+
105122#pragma mark - UIKit callbacks
106123
107124- (void )didMoveToParentViewController : (UIViewController *)parent
@@ -278,11 +295,6 @@ - (void)onDidPreventUserFromSelectingViewControllerWithKey:(nonnull NSString *)s
278295
279296- (BOOL )shouldPreventNativeTabSelection : (nonnull UIViewController *)nextViewController
280297{
281- // This handles the tabsHostComponentView nullability
282- if ([self .tabsHostComponentView experimental_controlNavigationStateInJS ]) {
283- return YES ;
284- }
285-
286298 if (![nextViewController isKindOfClass: RNSTabsScreenViewController.class ]) {
287299 // Allow for more view controller selection
288300 return NO ;
@@ -324,6 +336,13 @@ - (BOOL)tabBarController:(UITabBarController *)tabBarController
324336 return NO ;
325337 }
326338
339+ // This handles the tabsHostComponentView nullability
340+ // TODO: This if is likely to be removed, since we want to roll back the support
341+ // for "controlled mode", at least initially.
342+ if ([self .tabsHostComponentView experimental_controlNavigationStateInJS ]) {
343+ return NO ;
344+ }
345+
327346 BOOL shouldPreventTabSelection = [self shouldPreventNativeTabSelection: viewController];
328347
329348 if (shouldPreventTabSelection) {
@@ -729,17 +748,30 @@ - (void)setupMoreNavigationControllerDelegateIfNeeded
729748
730749// / Creates a dynamic subclass of the runtime class of `moreNavigationController`
731750// / (which is the private `UIMoreNavigationController`) and overrides `pushViewController:animated:`
732- // / with our gating implementation. The dynamic subclass is created once and reused.
751+ // / with our gating implementation.
752+ // /
753+ // / The subclass name is derived from the actual runtime class of `moreNavigationController`
754+ // / (e.g. `RNS_UIMoreNavigationController`), so if another library ISA-swizzles it first or Apple
755+ // / changes the private class, each distinct original class gets its own correct dynamic subclass.
733756- (void )installPushInterceptorOnMoreNavigationController
734757{
735758#if RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE
736- static const char *kDynamicSubclassName = " RNS_UIMoreNavigationController" ;
737- Class dynamicSubclass = objc_getClass (kDynamicSubclassName );
759+ RCTAssert (
760+ _didInstallPushInterceptor == NO ,
761+ @" [RNScreens] installPushInterceptorOnMoreNavigationController MUST NOT be called twice" );
762+
763+ Class originalClass = object_getClass (self.moreNavigationController );
764+ const char *originalClassName = class_getName (originalClass);
765+
766+ // Build a unique subclass name per original runtime class: "RNS_<originalClassName>"
767+ char dynamicSubclassName[256 ];
768+ snprintf (dynamicSubclassName, sizeof (dynamicSubclassName), " RNS_%s" , originalClassName);
769+
770+ Class dynamicSubclass = objc_getClass (dynamicSubclassName);
738771
739772 if (dynamicSubclass == nil ) {
740- Class originalClass = object_getClass (self.moreNavigationController );
741- dynamicSubclass = objc_allocateClassPair (originalClass, kDynamicSubclassName , 0 );
742- RCTAssert (dynamicSubclass != nil , @" [RNScreens] Failed to allocate dynamic subclass of %@ " , originalClass);
773+ dynamicSubclass = objc_allocateClassPair (originalClass, dynamicSubclassName, 0 );
774+ RCTAssert (dynamicSubclass != nil , @" [RNScreens] Failed to allocate dynamic subclass of %s " , originalClassName);
743775
744776 Method pushMethod = class_getInstanceMethod (originalClass, @selector (pushViewController:animated: ));
745777 class_addMethod (
@@ -757,6 +789,7 @@ - (void)installPushInterceptorOnMoreNavigationController
757789 // OBJC_ASSOCIATION_ASSIGN: no retain cycle — self owns moreNavigationController and outlives it.
758790 objc_setAssociatedObject (
759791 self.moreNavigationController , kRNSTabBarControllerAssociationKey , self, OBJC_ASSOCIATION_ASSIGN );
792+ _didInstallPushInterceptor = YES ;
760793#endif // RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE
761794}
762795
0 commit comments