Skip to content

Commit

Permalink
Make the Fabric Interop layer work in Bridgeless (#40732)
Browse files Browse the repository at this point in the history
Summary:

This change allows the Fabric Interop Layer to work in bridgeless mode.

Given that the legacy components requires a Bridge to send events and use commands, this change simulates a bridge when it doesn't exists (i.e.: we are in bridgeless mode).

In order to make it work, we had to simulate a few elements from the Bridge and the UIManager.

## Changelog:
[iOS][Fixed] - Make the Fabric interop layer works in bridgeless mode.

## Facebook:
As an alternative approach, we could have created a `protocol RCTBridging`, have the `RCTBridge` conform to that protocol, and create a new type for it.
Practically this would have been much more cumbersome:
1. The [RCTBridge](https://www.internalfb.com/code/fbsource/[916531b9bf7a9943036807f7563c925b4c3e0101]/xplat/js/react-native-github/packages/react-native/React/Base/RCTBridge.h?lines=87-238) interface is quite big. All the props and method should be part of the protocol.
2. Extensions declared on RCTBridge would not have worked. For example, [`RCTBridge (RCTUIManager)`](https://www.internalfb.com/code/fbsource/[916531b9bf7a9943036807f7563c925b4c3e0101]/xplat/js/react-native-github/packages/react-native/React/Modules/RCTUIManager.h?lines=170-174).
3. It would require a major overhaul of the APIs, returning `id<RCTBridging>` in place of **every** function that takes/return an instance of `RCTBridge *`.

Clearly, not a feasible way to go.

Differential Revision: D50079929
  • Loading branch information
Riccardo Cipolleschi authored and facebook-github-bot committed Oct 9, 2023
1 parent 60f5a80 commit 242ad6d
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 11 deletions.
8 changes: 7 additions & 1 deletion packages/react-native/React/Base/RCTBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,13 @@ - (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)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;
}
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native/React/Modules/RCTUIManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/React/Views/RCTComponentData.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ - (RCTViewManager *)manager
object:nil
userInfo:@{@"module" : _bridgelessViewManager}];
}
return _manager ?: _bridgelessViewManager;
return _manager ? _manager : _bridgelessViewManager;
}

RCT_NOT_IMPLEMENTED(-(instancetype)init)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,84 @@
#include <React/RCTUIManager.h>
#include <React/RCTUIManagerUtils.h>
#include <React/RCTUtils.h>
#include <React/RCTViewManager.h>
#include <folly/json.h>
#include <objc/runtime.h>

#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<NSNumber *, UIView *> *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.
Expand All @@ -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(
Expand All @@ -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 : @{}));
}
}
};
Expand Down Expand Up @@ -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];
}
}

Expand Down Expand Up @@ -169,8 +235,41 @@ - (void)removeViewFromRegistryWithTag:(NSInteger)tag
}

#pragma mark - Private
- (void)_handleCommandsOnBridge:(id<RCTBridgeMethod>)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<RCTBridgeMethod>)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<NSNumber *, UIView *> *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
Expand All @@ -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
{
Expand Down

0 comments on commit 242ad6d

Please sign in to comment.