Skip to content

Commit

Permalink
Add support for distributed registration of Fabric Components with Dy…
Browse files Browse the repository at this point in the history
…namic Libraries (#37274)

Summary:
Pull Request resolved: #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.

Reviewed By: dmytrorykun

Differential Revision: D45605441

fbshipit-source-id: 71c7a506341a5c56b96d89d9c8295ad97e958599
  • Loading branch information
cipolleschi authored and facebook-github-bot committed May 15, 2023
1 parent d8ced6f commit 191c216
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ extern "C" {
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name);
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupFuncs}
#endif
#ifdef __cplusplus
}
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ const FileTemplate = ({lookupMap}: {lookupMap: string}) => `
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sFabricComponentsClassMap = {
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupMap}
#endif
};
auto p = sFabricComponentsClassMap.find(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ extern \\"C\\" {
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name);
#ifndef RCT_DYNAMIC_FRAMEWORKS
Class<RCTComponentViewProtocol> NoPropsNoEventsComponentCls(void) __attribute__((used)); // NO_PROPS_NO_EVENTS
Class<RCTComponentViewProtocol> InterfaceOnlyComponentCls(void) __attribute__((used)); // INTERFACE_ONLY
Class<RCTComponentViewProtocol> BooleanPropNativeComponentCls(void) __attribute__((used)); // BOOLEAN_PROP
Expand Down Expand Up @@ -55,6 +57,8 @@ Class<RCTComponentViewProtocol> ExcludedAndroidComponentCls(void) __attribute__(
Class<RCTComponentViewProtocol> MultiFileIncludedNativeComponentCls(void) __attribute__((used)); // EXCLUDE_IOS_TWO_COMPONENTS_DIFFERENT_FILES
#endif
#ifdef __cplusplus
}
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Map {
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sFabricComponentsClassMap = {
#ifndef RCT_DYNAMIC_FRAMEWORKS
{\\"NoPropsNoEventsComponent\\", NoPropsNoEventsComponentCls}, // NO_PROPS_NO_EVENTS
Expand Down Expand Up @@ -80,6 +81,7 @@ Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char
{\\"MultiFileIncludedNativeComponent\\", MultiFileIncludedNativeComponentCls}, // EXCLUDE_IOS_TWO_COMPONENTS_DIFFERENT_FILES
#endif
};
auto p = sFabricComponentsClassMap.find(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
#import <react/renderer/components/view/ViewEventEmitter.h>
#import <react/renderer/components/view/ViewProps.h>

#ifdef RCT_DYNAMIC_FRAMEWORKS
#import <React/RCTComponentViewFactory.h>
#endif

using namespace facebook::react;

@implementation RCTViewComponentView {
Expand All @@ -30,6 +34,13 @@ @implementation RCTViewComponentView {
NSSet<NSString *> *_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]) {
Expand Down
11 changes: 11 additions & 0 deletions packages/react-native/scripts/cocoapods/new_architecture.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ + (ComponentDescriptorProvider)componentDescriptorProvider
return concreteComponentDescriptorProvider<RNTMyNativeViewComponentDescriptor>();
}

// 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]) {
Expand Down

0 comments on commit 191c216

Please sign in to comment.