From ce116b653b49e89bb1e8346f4010e1080eb8e98b Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Mon, 15 May 2023 08:14:59 -0700 Subject: [PATCH] Add support to register New Renderer components from the AppDelegate. (#37273) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37273 When using dynamic frameworks, we can't rely on Codegen to register all the components into the renderer. That's because, we would have to codegen a new target, which depends on ALL the 3rd party dependencies that expose a UI component. The previous PR adds support for distributed automatic registration when components are loaded in memory. However, not to slow down the adoption of the New Architecture, there could be apps that need to register a component that does not support the distributed approach yet. Thanks to this method, apps can register those components. ## Changelog: [iOS][Added] - Added a mechanism to register components into the renderer from the user app. Reviewed By: dmytrorykun Differential Revision: D45605688 fbshipit-source-id: 3583913f2700be4d6cb33a862429486aca675acf --- .../Libraries/AppDelegate/RCTAppDelegate.h | 9 +++++++ .../Libraries/AppDelegate/RCTAppDelegate.mm | 15 +++++++++++- .../Fabric/Mounting/RCTComponentViewFactory.h | 16 +++++++++++++ .../Mounting/RCTComponentViewFactory.mm | 16 +++++++++++-- .../scripts/cocoapods/new_architecture.rb | 1 - packages/rn-tester/RNTester/AppDelegate.mm | 24 +++++++++++++++++++ 6 files changed, 77 insertions(+), 4 deletions(-) diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h index 93b1543c7ba..2eb02eafb85 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h @@ -9,6 +9,7 @@ #import #import +@protocol RCTComponentViewProtocol; @class RCTSurfacePresenterBridgeAdapter; /** @@ -96,6 +97,14 @@ #if RCT_NEW_ARCH_ENABLED @property (nonatomic, strong) RCTSurfacePresenterBridgeAdapter *bridgeAdapter; +/// This method returns a map of Component Descriptors and Components classes that needs to be registered in the +/// new renderer. The Component Descriptor is a string which represent the name used in JS to refer to the native +/// component. The default implementation returns an empty dictionary. Subclasses can override this method to register +/// the required components. +/// +/// @return a dictionary that associate a component for the new renderer with his descriptor. +- (NSDictionary> *)thirdPartyFabricComponents; + /// This method controls whether the `turboModules` feature of the New Architecture is turned on or off. /// /// @note: This is required to be rendering on Fabric (i.e. on the New Architecture). diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index fffea76a6c3..c04d5e3fed0 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -11,6 +11,8 @@ #if RCT_NEW_ARCH_ENABLED #import +#import +#import #import #import #import @@ -24,7 +26,10 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; -@interface RCTAppDelegate () { +@interface RCTAppDelegate () < + RCTTurboModuleManagerDelegate, + RCTCxxBridgeDelegate, + RCTComponentViewFactoryComponentProvider> { std::shared_ptr _reactNativeConfig; facebook::react::ContextContainer::Shared _contextContainer; std::shared_ptr _runtimeScheduler; @@ -64,6 +69,7 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter; [self unstable_registerLegacyComponents]; + [RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self; #endif NSDictionary *initProps = [self prepareInitialProps]; @@ -163,6 +169,13 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; return RCTAppSetupDefaultModuleFromClass(moduleClass); } +#pragma mark - RCTComponentViewFactoryComponentProvider + +- (NSDictionary> *)thirdPartyFabricComponents +{ + return @{}; +} + #pragma mark - New Arch Enabled settings - (BOOL)turboModuleEnabled diff --git a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.h b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.h index 771131d50f4..15eb8f0b4c8 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.h +++ b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.h @@ -16,12 +16,28 @@ NS_ASSUME_NONNULL_BEGIN void RCTInstallNativeComponentRegistryBinding(facebook::jsi::Runtime &runtime); +/** + * Protocol that can be implemented to provide some 3rd party components to Fabric. + * Fabric will check in this map whether there are some components that need to be registered. + */ +@protocol RCTComponentViewFactoryComponentProvider + +/** + * Return a dictionary of third party components where the `key` is the Component Handler and the `value` is a Class + * that conforms to `RCTComponentViewProtocol`. + */ +- (NSDictionary> *)thirdPartyFabricComponents; + +@end + /** * Registry of supported component view classes that can instantiate * view component instances by given component handle. */ @interface RCTComponentViewFactory : NSObject +@property (nonatomic, weak) id thirdPartyFabricComponentsProvider; + /** * Constructs and returns an instance of the class with a bunch of already registered standard components. */ diff --git a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm index 216c2c0b332..8ed6f9a8319 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm @@ -112,7 +112,19 @@ static Class RCTComponentViewClassWithName(const char return YES; } - // Fallback 2: Try to use Paper Interop. + // Fallback 2: Ask the provider and check in the dictionary provided + if (self.thirdPartyFabricComponentsProvider) { + // Test whether a provider has been passed to avoid potentially expensive conversions + // between C++ and ObjC strings. + NSString *objcName = [NSString stringWithCString:name.c_str() encoding:NSUTF8StringEncoding]; + klass = self.thirdPartyFabricComponentsProvider.thirdPartyFabricComponents[objcName]; + if (klass) { + [self registerComponentViewClass:klass]; + return YES; + } + } + + // Fallback 3: Try to use Paper Interop. NSString *componentNameString = RCTNSStringFromString(name); if ([RCTLegacyViewManagerInteropComponentView isSupported:componentNameString]) { RCTLogNewArchitectureValidation( @@ -136,7 +148,7 @@ static Class RCTComponentViewClassWithName(const char return YES; } - // Fallback 3: use if component doesn't exist. + // Fallback 4: use if component doesn't exist. auto flavor = std::make_shared(name); auto componentName = ComponentName{flavor->c_str()}; auto componentHandle = reinterpret_cast(componentName); diff --git a/packages/react-native/scripts/cocoapods/new_architecture.rb b/packages/react-native/scripts/cocoapods/new_architecture.rb index 471017aa39f..a6eac31d9c5 100644 --- a/packages/react-native/scripts/cocoapods/new_architecture.rb +++ b/packages/react-native/scripts/cocoapods/new_architecture.rb @@ -70,7 +70,6 @@ class NewArchitectureHelper # Set "RCT_DYNAMIC_FRAMEWORKS=1" if pod are installed with USE_FRAMEWORKS=dynamic # This helps with backward compatibility. if pod_name == 'React-RCTFabric' && ENV['USE_FRAMEWORKS'] == 'dynamic' - Pod::UI.puts "Adding RCT_DYNAMIC_FRAMEWORKS=1 to React-RCTFabric".yellow rct_dynamic_framework_flag = " -DRCT_DYNAMIC_FRAMEWORKS=1" target_installation_result.native_target.build_configurations.each do |config| prev_build_settings = config.build_settings['OTHER_CPLUSPLUSFLAGS'] != nil ? config.build_settings['OTHER_CPLUSPLUSFLAGS'] : "$(inherithed)" diff --git a/packages/rn-tester/RNTester/AppDelegate.mm b/packages/rn-tester/RNTester/AppDelegate.mm index d3f3f355f94..bb1cbe68f73 100644 --- a/packages/rn-tester/RNTester/AppDelegate.mm +++ b/packages/rn-tester/RNTester/AppDelegate.mm @@ -49,6 +49,7 @@ #endif #ifdef RN_FABRIC_ENABLED +#import #import #import #import @@ -60,6 +61,10 @@ #import #endif +#if RCT_NEW_ARCH_ENABLED +#import +#endif + #if DEBUG #ifdef FB_SONARKIT_ENABLED #import @@ -85,6 +90,12 @@ } @end +#if RCT_NEW_ARCH_ENABLED +/// Declare conformance to `RCTComponentViewFactoryComponentProvider` +@interface AppDelegate () +@end +#endif + static NSString *const kRNConcurrentRoot = @"concurrentRoot"; @implementation AppDelegate @@ -110,6 +121,10 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; // Appetizer.io params check NSDictionary *initProps = [self prepareInitialProps]; +#if RCT_NEW_ARCH_ENABLED + [RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self; +#endif + #ifdef RN_FABRIC_ENABLED _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:_bridge contextContainer:_contextContainer]; @@ -333,4 +348,13 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; #endif +#pragma mark - RCTComponentViewFactoryComponentProvider + +#if RCT_NEW_ARCH_ENABLED +- (nonnull NSDictionary> *)thirdPartyFabricComponents +{ + return @{@"RNTMyNativeView" : RNTMyNativeViewComponentView.class}; +} +#endif + @end