From c217037e97f601088c1b150393c32699323bdf3b Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Sat, 6 May 2023 07:06:27 -0700 Subject: [PATCH] Add support for distributed registration of Fabric Components with Dynamic Libraries (#37274) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37274 With dynamic frameworks, we can't use floating C functions. The way in which dynamic frameworks work is that they need to be self contained. They are built in isolation so that other frameworks can be linked against them to solve their dependencies. Currently, when working with 3rd party libraries, we are Codegenerating a RCTThirdPartyComponentProvider which tries to invoke floating C functions that are defined in other modules. React-RCTFabric has no visibility on those modules, therefore it fails building. The implemented solution exclude the generation of those symbols and leverage a the Objective-C runtime to automatically register libraries when they are loaded. **This mechanism is applied ONLY when the flag RCT_DYNAMIC_FRAMEWORKS is turned on.** There will be no impact on internal meta apps, nor on any apps that are not using Dynamic Frameworks. This change requires a small migration in all the Fabric components libraries that wants to support dynamic frameworks. They have to implement a ``` + (void)load { [super load]; } ``` method in their ComponentView. Not to slow down the adoption of the new architecture, waiting for a migration in the ecosystem, the next diff introduce a secondary, declarative loading mechanism for Fabric Components, which follows the same approach used by TurboModules. ## Changelog: [iOS][Changed] - Add support for distributed registration of Fabric Components with Dynamic Libraries. Notes that this change is NOT breaking as dynamic frameworks were not working before in the New Architecture. Static Libraries and Static Frameworks continue working as usual. Differential Revision: D45605441 fbshipit-source-id: 309b85e55c0f699505877a77a8d781202f47e7af --- .../GenerateThirdPartyFabricComponentsProviderH.js | 4 ++++ ...enerateThirdPartyFabricComponentsProviderObjCpp.js | 2 ++ ...teThirdPartyFabricComponentsProviderH-test.js.snap | 4 ++++ ...rdPartyFabricComponentsProviderObjCpp-test.js.snap | 2 ++ .../ComponentViews/View/RCTViewComponentView.mm | 11 +++++++++++ .../scripts/cocoapods/new_architecture.rb | 11 +++++++++++ .../ios/RNTMyNativeViewComponentView.mm | 9 +++++++++ 7 files changed, 43 insertions(+) diff --git a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js index cddb1302d754a4..40b168372af7c1 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js +++ b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js @@ -36,8 +36,12 @@ extern "C" { Class RCTThirdPartyFabricComponentsProvider(const char *name); +#ifndef RCT_DYNAMIC_FRAMEWORKS + ${lookupFuncs} +#endif + #ifdef __cplusplus } #endif diff --git a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js index fcd7b528192635..06e964845d3f52 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js +++ b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js @@ -34,7 +34,9 @@ const FileTemplate = ({lookupMap}: {lookupMap: string}) => ` Class RCTThirdPartyFabricComponentsProvider(const char *name) { static std::unordered_map sFabricComponentsClassMap = { + #ifndef RCT_DYNAMIC_FRAMEWORKS ${lookupMap} + #endif }; auto p = sFabricComponentsClassMap.find(name); diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap index 82f6247e94f4f0..f1b25981b31701 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap @@ -23,6 +23,8 @@ extern \\"C\\" { Class RCTThirdPartyFabricComponentsProvider(const char *name); +#ifndef RCT_DYNAMIC_FRAMEWORKS + Class NoPropsNoEventsComponentCls(void) __attribute__((used)); // NO_PROPS_NO_EVENTS Class InterfaceOnlyComponentCls(void) __attribute__((used)); // INTERFACE_ONLY Class BooleanPropNativeComponentCls(void) __attribute__((used)); // BOOLEAN_PROP @@ -55,6 +57,8 @@ Class ExcludedAndroidComponentCls(void) __attribute__( Class MultiFileIncludedNativeComponentCls(void) __attribute__((used)); // EXCLUDE_IOS_TWO_COMPONENTS_DIFFERENT_FILES +#endif + #ifdef __cplusplus } #endif diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap index c766a6b679a9b5..eb6d622917a2d0 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap @@ -21,6 +21,7 @@ Map { Class RCTThirdPartyFabricComponentsProvider(const char *name) { static std::unordered_map sFabricComponentsClassMap = { + #ifndef RCT_DYNAMIC_FRAMEWORKS {\\"NoPropsNoEventsComponent\\", NoPropsNoEventsComponentCls}, // NO_PROPS_NO_EVENTS @@ -80,6 +81,7 @@ Class RCTThirdPartyFabricComponentsProvider(const char {\\"MultiFileIncludedNativeComponent\\", MultiFileIncludedNativeComponentCls}, // EXCLUDE_IOS_TWO_COMPONENTS_DIFFERENT_FILES + #endif }; auto p = sFabricComponentsClassMap.find(name); diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 60e0697071d8ee..e2239c99531559 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -18,6 +18,10 @@ #import #import +#ifdef RCT_DYNAMIC_FRAMEWORKS +#import +#endif + using namespace facebook::react; @implementation RCTViewComponentView { @@ -30,6 +34,13 @@ @implementation RCTViewComponentView { NSSet *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN; } +#ifdef RCT_DYNAMIC_FRAMEWORKS ++ (void)load +{ + [RCTComponentViewFactory.currentComponentViewFactory registerComponentViewClass:self]; +} +#endif + - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { diff --git a/packages/react-native/scripts/cocoapods/new_architecture.rb b/packages/react-native/scripts/cocoapods/new_architecture.rb index 66aea069815917..471017aa39f80f 100644 --- a/packages/react-native/scripts/cocoapods/new_architecture.rb +++ b/packages/react-native/scripts/cocoapods/new_architecture.rb @@ -67,6 +67,17 @@ def self.modify_flags_for_new_architecture(installer, is_new_arch_enabled) end end + # 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)" + config.build_settings['OTHER_CPLUSPLUSFLAGS'] = prev_build_settings + rct_dynamic_framework_flag + end + end + target_installation_result.native_target.build_configurations.each do |config| if config.name == "Release" current_flags = config.build_settings['OTHER_CPLUSPLUSFLAGS'] != nil ? config.build_settings['OTHER_CPLUSPLUSFLAGS'] : "$(inherited)" diff --git a/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm b/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm index 33e7a7f2e8b263..81dd9f04ba6a43 100644 --- a/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm +++ b/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm @@ -7,6 +7,7 @@ #import "RNTMyNativeViewComponentView.h" +#import #import #import #import @@ -28,6 +29,14 @@ + (ComponentDescriptorProvider)componentDescriptorProvider return concreteComponentDescriptorProvider(); } +// Load is not invoked if it is not defined, therefore, we must ask to update this. +// See the Apple documentation: https://developer.apple.com/documentation/objectivec/nsobject/1418815-load?language=objc +// "[...] but only if the newly loaded class or category implements a method that can respond." ++ (void)load +{ + [super load]; +} + - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) {