diff --git a/packages/react-native/React/Base/RCTBridge.mm b/packages/react-native/React/Base/RCTBridge.mm index d0598e621e22f2..605ab95bf583d6 100644 --- a/packages/react-native/React/Base/RCTBridge.mm +++ b/packages/react-native/React/Base/RCTBridge.mm @@ -204,7 +204,13 @@ - (instancetype)initWithDelegate:(id)delegate _moduleProvider = block; _launchOptions = [launchOptions copy]; - [self setUp]; + // For the interop layer in bridgeless mode, we need to simulate the Bridge + // We can do that by subclassing the bridge. But, in that case, we don't really want to + // setup the bridge completely. So, we will skip setting up the bridge in that case. + BOOL shouldSkipSetup = [((NSNumber *)launchOptions[@"InteropLayer_skipSetup"]) boolValue]; + if (!shouldSkipSetup) { + [self setUp]; + } } return self; } diff --git a/packages/react-native/React/Modules/RCTUIManager.m b/packages/react-native/React/Modules/RCTUIManager.m index 7027921fda706b..2d6459215d3008 100644 --- a/packages/react-native/React/Modules/RCTUIManager.m +++ b/packages/react-native/React/Modules/RCTUIManager.m @@ -502,6 +502,14 @@ - (void)addUIBlock:(RCTViewManagerUIBlock)block return; } + // When it comes to the interop layer in bridgeless mode + // this array could start as nil. In that case, + // we create a new array to start tracking the block we need + // to execute + if (!_pendingUIBlocks) { + _pendingUIBlocks = [NSMutableArray new]; + } + [_pendingUIBlocks addObject:block]; } diff --git a/packages/react-native/React/Views/RCTComponentData.m b/packages/react-native/React/Views/RCTComponentData.m index 888cad59b88e27..52efad6e159d21 100644 --- a/packages/react-native/React/Views/RCTComponentData.m +++ b/packages/react-native/React/Views/RCTComponentData.m @@ -71,7 +71,7 @@ - (RCTViewManager *)manager object:nil userInfo:@{@"module" : _bridgelessViewManager}]; } - return _manager ?: _bridgelessViewManager; + return _manager ? _manager : _bridgelessViewManager; } RCT_NOT_IMPLEMENTED(-(instancetype)init) diff --git a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm index 72e17982dd0b32..dea2196a2294e0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm +++ b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm @@ -16,15 +16,84 @@ #include #include #include +#include #include #include +#pragma mark - Helpers for Bridgeless Mode + +@implementation RCTViewManager (InteropLayer) + +// setBridge throws an error when used in bridgeless mode +// However, the interop layer needs to simulate the bridge and to set it +// to the 3rd parties view managers. +- (void)setFakeBridge:(RCTBridge *)bridge +{ + self.bridge = bridge; +} + +@end + +// We need access to some private properties and methods of the UIManager to allow the +// interop layer in the bridgeless mode to work properly. +// Using a category, we can create an interface that, at runtime, will tap into the +// original implementation. This allows us to call private methods without having to +// expose them. +@interface RCTUIManager (InteropLayer) + +@property (nonatomic, assign, readonly) NSMutableDictionary *viewRegistry; +- (void)flushUIBlocksWithCompletion:(void (^)(void))completion; + +@end + +// The RCTFakeBridge is a subclass of the RCTBridge. In this case we can provide a bridge to the +// 3rd party components that needs it even in the bridgeless mode. +// We can also use this subclass to customize how the bridge behaves. +@interface RCTFakeBridge : RCTBridge +@property (readonly, atomic) RCTUIManager *uiManager; +@end + +@implementation RCTFakeBridge + +- (instancetype)init +{ + // Purposedly skip initializing the RCTBridge. + self = [super initWithDelegate:nil launchOptions:@{@"InteropLayer_skipSetup" : @YES}]; + if (self) { + _uiManager = [[RCTUIManager alloc] init]; + // Set value for key (a.k.a: KVC) can bypass `readonoly`. ¯\_(ツ)_/¯ + [_uiManager setValue:@{}.mutableCopy forKey:@"viewRegistry"]; + } + return self; +} + +- (void)addUIBlock:(RCTViewManagerUIBlock)block +{ + __weak __typeof(self) weakSelf = self; + RCTExecuteOnUIManagerQueue(^{ + __typeof(self) strongSelf = weakSelf; + [strongSelf.uiManager addUIBlock:block]; + [strongSelf flushUIBlocks]; + }); +} + +- (void)flushUIBlocks +{ + [self.uiManager flushUIBlocksWithCompletion:^{ + }]; +} + +@end + +#pragma mark - RCTLegacyViewManagerInteropCoordinator + using namespace facebook::react; @implementation RCTLegacyViewManagerInteropCoordinator { RCTComponentData *_componentData; __weak RCTBridge *_bridge; __weak RCTBridgeModuleDecorator *_bridgelessInteropData; + RCTFakeBridge *_fakeBridge; /* Each instance of `RCTLegacyViewManagerInteropComponentView` registers a block to which events are dispatched. This is the container that maps unretained UIView pointer to a block to which the event is dispatched. @@ -47,6 +116,7 @@ - (instancetype)initWithComponentData:(RCTComponentData *)componentData _componentData = componentData; _bridge = bridge; _bridgelessInteropData = bridgelessInteropData; + _fakeBridge = [[RCTFakeBridge alloc] init]; if (bridgelessInteropData) { // During bridge mode, RCTBridgeModules will be decorated with these APIs by the bridge. RCTAssert( @@ -62,7 +132,9 @@ - (instancetype)initWithComponentData:(RCTComponentData *)componentData if (strongSelf) { InterceptorBlock block = [strongSelf->_eventInterceptors objectForKey:reactTag]; if (block) { - block(std::string([RCTNormalizeInputEventName(eventName) UTF8String]), convertIdToFollyDynamic(event ?: @{})); + block( + std::string([RCTNormalizeInputEventName(eventName) UTF8String]), + convertIdToFollyDynamic(event ? event : @{})); } } }; @@ -131,15 +203,9 @@ - (void)handleCommand:(NSString *)commandName NSArray *newArgs = [@[ [NSNumber numberWithInteger:tag] ] arrayByAddingObjectsFromArray:args]; if (_bridge) { - [_bridge.batchedBridge - dispatchBlock:^{ - [method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs]; - [self->_bridge.uiManager setNeedsLayout]; - } - queue:RCTGetUIManagerQueue()]; + [self _handleCommandsOnBridge:method withArgs:newArgs]; } else { - // TODO T86826778 - Figure out which queue this should be dispatched to. - [method invokeWithBridge:nil module:self->_componentData.manager arguments:newArgs]; + [self _handleCommandsOnBridgeless:method withArgs:newArgs]; } } @@ -169,8 +235,41 @@ - (void)removeViewFromRegistryWithTag:(NSInteger)tag } #pragma mark - Private +- (void)_handleCommandsOnBridge:(id)method withArgs:(NSArray *)newArgs +{ + [_bridge.batchedBridge + dispatchBlock:^{ + [method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs]; + [self->_bridge.uiManager setNeedsLayout]; + } + queue:RCTGetUIManagerQueue()]; +} + +- (void)_handleCommandsOnBridgeless:(id)method withArgs:(NSArray *)newArgs +{ + [self->_componentData.manager setFakeBridge:_fakeBridge]; + RCTViewManager *componentViewManager = self->_componentData.manager; + + __weak __typeof__(self) weakSelf = self; + [self->_fakeBridge addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTExecuteOnUIManagerQueue(^{ + __typeof__(self) strongSelf = weakSelf; + [method invokeWithBridge:nil module:componentViewManager arguments:newArgs]; + [strongSelf->_fakeBridge flushUIBlocks]; + }); + }]; +} - (void)_addUIBlock:(RCTViewManagerUIBlock)block +{ + if (_bridge) { + [self _addUIBlockOnBridge:block]; + } else { + [self _addUIBlockOnBridgeless:block]; + } +} + +- (void)_addUIBlockOnBridge:(RCTViewManagerUIBlock)block { __weak __typeof__(self) weakSelf = self; [_bridge.batchedBridge @@ -181,6 +280,15 @@ - (void)_addUIBlock:(RCTViewManagerUIBlock)block queue:RCTGetUIManagerQueue()]; } +- (void)_addUIBlockOnBridgeless:(RCTViewManagerUIBlock)block +{ + __weak __typeof__(self) weakSelf = self; + RCTExecuteOnUIManagerQueue(^{ + __typeof__(self) strongSelf = weakSelf; + [strongSelf->_fakeBridge addUIBlock:block]; + }); +} + // This is copy-pasta from RCTModuleData. - (void)_lookupModuleMethodsIfNecessary {