From 6c9d6275944f66fbcf78176ec50748f493c4b54f Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Mon, 31 Aug 2026 10:39:56 +0300 Subject: [PATCH] Fixed React surface observer lifecycle --- ios/RNNReactButtonView.mm | 1 - ios/RNNReactView.mm | 42 ++++++++++----- .../NavigationTests/RNNReactButtonViewTest.mm | 54 +++++++++++++++++++ .../ios/playground.xcodeproj/project.pbxproj | 4 ++ 4 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 playground/ios/NavigationTests/RNNReactButtonViewTest.mm diff --git a/ios/RNNReactButtonView.mm b/ios/RNNReactButtonView.mm index af802d3874..59f3739f57 100644 --- a/ios/RNNReactButtonView.mm +++ b/ios/RNNReactButtonView.mm @@ -17,7 +17,6 @@ - (instancetype)initWithHost:(RCTHost *)host sizeMeasureMode:(RCTSurfaceSizeMeasureMode)sizeMeasureMode reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock { self = [super initWithHost:host moduleName:moduleName initialProperties:initialProperties eventEmitter:eventEmitter sizeMeasureMode:convertToSurfaceSizeMeasureMode(RCTRootViewSizeFlexibilityWidthAndHeight) reactViewReadyBlock:reactViewReadyBlock]; - [host.surfacePresenter addObserver:self]; self.backgroundColor = [UIColor clearColor]; if (@available(iOS 26.0, *)) { diff --git a/ios/RNNReactView.mm b/ios/RNNReactView.mm index 099cb72338..1d74bd0f2e 100644 --- a/ios/RNNReactView.mm +++ b/ios/RNNReactView.mm @@ -15,6 +15,9 @@ @implementation RNNReactView { BOOL _pendingDidAppear; BOOL _didAppear; BOOL _willAppear; +#ifdef RCT_NEW_ARCH_ENABLED + __weak RCTSurfacePresenter *_surfacePresenter; +#endif } #ifdef RCT_NEW_ARCH_ENABLED @@ -38,17 +41,23 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge self = [super initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; #endif - _reactViewReadyBlock = reactViewReadyBlock; - _eventEmitter = eventEmitter; + if (self) { +#ifdef RCT_NEW_ARCH_ENABLED + _surfacePresenter = (RCTSurfacePresenter *)bridge.surfacePresenter; + [_surfacePresenter addObserver:self]; +#endif + _reactViewReadyBlock = reactViewReadyBlock; + _eventEmitter = eventEmitter; #ifdef RCT_NEW_ARCH_ENABLED - [surface start]; + [surface start]; #else - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(contentDidAppear:) - name:RCTContentDidAppearNotification - object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(contentDidAppear:) + name:RCTContentDidAppearNotification + object:nil]; #endif + } return self; } @@ -63,16 +72,25 @@ - (instancetype)initWithHost:(RCTHost *)host RCTFabricSurface *surface = [host createSurfaceWithModuleName:moduleName initialProperties:initialProperties]; - [host.surfacePresenter addObserver:self]; self = [super initWithSurface:surface sizeMeasureMode:sizeMeasureMode]; - - _reactViewReadyBlock = reactViewReadyBlock; - _eventEmitter = eventEmitter; - + if (self) { + _surfacePresenter = host.surfacePresenter; + [_surfacePresenter addObserver:self]; + + _reactViewReadyBlock = reactViewReadyBlock; + _eventEmitter = eventEmitter; + } + return self; } #endif +- (void)dealloc { +#ifdef RCT_NEW_ARCH_ENABLED + [_surfacePresenter removeObserver:self]; +#endif +} + #ifdef RCT_NEW_ARCH_ENABLED #pragma mark - RCTSurfaceDelegate - (void)surface:(__unused RCTSurface *)surface didChangeStage:(RCTSurfaceStage)stage { diff --git a/playground/ios/NavigationTests/RNNReactButtonViewTest.mm b/playground/ios/NavigationTests/RNNReactButtonViewTest.mm new file mode 100644 index 0000000000..8e20fcd068 --- /dev/null +++ b/playground/ios/NavigationTests/RNNReactButtonViewTest.mm @@ -0,0 +1,54 @@ +#import +#import +#import + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#import +#import + +@interface RNNSurfacePresenterSpy : NSObject +@property(nonatomic) NSInteger addCount; +@end + +@implementation RNNSurfacePresenterSpy + +- (void)addObserver:(__unused id)observer { + self.addCount += 1; +} + +- (void)removeObserver:(__unused id)observer { +} + +@end +#endif + +@interface RNNReactButtonViewTest : XCTestCase +@end + +@implementation RNNReactButtonViewTest + +#ifdef RCT_NEW_ARCH_ENABLED +- (void)testRegistersSurfacePresenterObserverOnce { + id host = OCMClassMock([RCTHost class]); + id surface = OCMClassMock([RCTFabricSurface class]); + RNNSurfacePresenterSpy *surfacePresenter = [RNNSurfacePresenterSpy new]; + OCMStub([host createSurfaceWithModuleName:@"Button" initialProperties:@{}]).andReturn(surface); + OCMStub([host surfacePresenter]).andReturn(surfacePresenter); + + RNNReactButtonView *view = [[RNNReactButtonView alloc] + initWithHost:host + moduleName:@"Button" + initialProperties:@{} + eventEmitter:nil + sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthUndefined | + RCTSurfaceSizeMeasureModeHeightUndefined + reactViewReadyBlock:nil]; + + XCTAssertNotNil(view); + XCTAssertEqual(surfacePresenter.addCount, 1); +} + +#endif + +@end diff --git a/playground/ios/playground.xcodeproj/project.pbxproj b/playground/ios/playground.xcodeproj/project.pbxproj index 4e76a4d4b3..fb1c9b3ed2 100644 --- a/playground/ios/playground.xcodeproj/project.pbxproj +++ b/playground/ios/playground.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 50996C6923AA487800008F89 /* RNNTestRootViewCreator.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58D263D2385888C003F36BA /* RNNTestRootViewCreator.mm */; }; 50BCB27623F1A2B100D6C8E5 /* TopBarAppearancePresenterTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50BCB27523F1A26600D6C8E5 /* TopBarAppearancePresenterTest.mm */; }; 50C085E62591341600B0502C /* RNNUIBarButtonItemTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50C085E52591341600B0502C /* RNNUIBarButtonItemTest.mm */; }; + C6FFE0D95634444585A67D66 /* RNNReactButtonViewTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B9ECE2F1FB6445390982D7F /* RNNReactButtonViewTest.mm */; }; 50C085E8259141E200B0502C /* RNNButtonsPresenterTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50C085E7259141E200B0502C /* RNNButtonsPresenterTest.mm */; }; 50C085F52594973000B0502C /* RNNComponentViewController+Utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50C9A8D3240FB9D000BD699F /* RNNComponentViewController+Utils.mm */; }; 50C9A8D1240EB95F00BD699F /* RNNBottomTabsController+Helpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50CF233C240695B10098042D /* RNNBottomTabsController+Helpers.mm */; }; @@ -159,6 +160,7 @@ 50996C6723AA477400008F89 /* RNNRootViewControllerTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNNRootViewControllerTest.mm; sourceTree = ""; }; 50BCB27523F1A26600D6C8E5 /* TopBarAppearancePresenterTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TopBarAppearancePresenterTest.mm; sourceTree = ""; }; 50C085E52591341600B0502C /* RNNUIBarButtonItemTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNNUIBarButtonItemTest.mm; sourceTree = ""; }; + 4B9ECE2F1FB6445390982D7F /* RNNReactButtonViewTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNNReactButtonViewTest.mm; sourceTree = ""; }; 50C085E7259141E200B0502C /* RNNButtonsPresenterTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNNButtonsPresenterTest.mm; sourceTree = ""; }; 50C9A8D2240FB9D000BD699F /* RNNComponentViewController+Utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNNComponentViewController+Utils.h"; sourceTree = ""; }; 50C9A8D3240FB9D000BD699F /* RNNComponentViewController+Utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "RNNComponentViewController+Utils.mm"; sourceTree = ""; }; @@ -403,6 +405,7 @@ E58D26412385888C003F36BA /* RNNCommandsHandlerTest.mm */, E58D26352385888B003F36BA /* RNNComponentPresenterTest.mm */, 50C085E52591341600B0502C /* RNNUIBarButtonItemTest.mm */, + 4B9ECE2F1FB6445390982D7F /* RNNReactButtonViewTest.mm */, 50C085E7259141E200B0502C /* RNNButtonsPresenterTest.mm */, E58D26402385888C003F36BA /* RNNViewControllerFactoryTest.mm */, E58D26272385888B003F36BA /* RNNDotIndicatorPresenterTest.mm */, @@ -985,6 +988,7 @@ E58D265E2385888C003F36BA /* RNNCommandsHandlerTest.mm in Sources */, E58D264B2385888C003F36BA /* RNNLayoutManagerTest.mm in Sources */, 50C085E62591341600B0502C /* RNNUIBarButtonItemTest.mm in Sources */, + C6FFE0D95634444585A67D66 /* RNNReactButtonViewTest.mm in Sources */, E58D26592385888C003F36BA /* RNNNavigationStackManagerTest.mm in Sources */, E58D26602385888C003F36BA /* RNNModalManagerTest.mm in Sources */, E58D26502385888C003F36BA /* RNNNavigationOptionsTest.mm in Sources */,