Skip to content

Commit 357ac58

Browse files
committed
fix: Allow module centralized registration (microsoft#2848)
<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The four fields below are mandatory. --> <!-- This fork of react-native provides React Native for macOS for the community. It also contains some changes that are required for usage internal to Microsoft. We are working on reducing the diff between Facebook's public version of react-native and our microsoft/react-native-macos fork. Long term, we want this fork to only contain macOS concerns and have the other iOS and Android concerns contributed upstream. If you are making a new change then one of the following should be done: - Consider if it is possible to achieve the desired behavior without making a change to microsoft/react-native-macos. Often a change can be made in a layer above in facebook/react-native instead. - Create a corresponding PR against [facebook/react-native](https://github.com/facebook/react-native) **Note:** Ideally you would wait for Facebook feedback before submitting to Microsoft, since we want to ensure that this fork doesn't deviate from upstream. --> ## Summary: <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> Objective-C `+load` method used for bridge module self-registration adds a performance tax when run for each of the multiple core modules during React instance initialization. This change allows registering the modules on a single pass when the instance is initialized using an explicit class name list. ## Test Plan: <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. --> All existing scenarios work the same as before. Performance improvements should be measurable via profiling.
1 parent 29539df commit 357ac58

3 files changed

Lines changed: 207 additions & 73 deletions

File tree

packages/react-native/React/Base/RCTBridge.mm

Lines changed: 183 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,195 @@
3232
#import "RCTReloadCommand.h"
3333
#import "RCTUtils.h"
3434

35+
// [macOS
36+
/**
37+
* List of core React Native modules.
38+
*
39+
* When RCT_MODULE_NO_SELF_LOAD is set to non-zero, module self-registration via +load is disabled.
40+
* Instead, RCTBridge will register these modules at initialization time.
41+
*/
42+
static NSArray<NSString *> *moduleClassNames = @[
43+
@"RCTViewManager",
44+
@"RCTActivityIndicatorViewManager",
45+
@"RCTDebuggingOverlayManager",
46+
@"RCTModalHostViewManager",
47+
@"RCTModalManager",
48+
@"RCTRefreshControlManager",
49+
@"RCTSafeAreaViewManager",
50+
@"RCTScrollContentViewManager",
51+
@"RCTScrollViewManager",
52+
@"RCTSwitchManager",
53+
@"RCTUIManager",
54+
@"RCTAccessibilityManager",
55+
@"RCTActionSheetManager",
56+
@"RCTAlertManager",
57+
@"RCTAppearance",
58+
@"RCTAppState",
59+
@"RCTClipboard",
60+
@"RCTDeviceInfo",
61+
@"RCTDevLoadingView",
62+
@"RCTDevMenu",
63+
@"RCTDevSettings",
64+
@"RCTDevToolsRuntimeSettingsModule",
65+
@"RCTEventDispatcher",
66+
@"RCTExceptionsManager",
67+
@"RCTI18nManager",
68+
@"RCTKeyboardObserver",
69+
@"RCTLogBox",
70+
@"RCTPerfMonitor",
71+
@"RCTPlatform",
72+
@"RCTRedBox",
73+
@"RCTSourceCode",
74+
@"RCTStatusBarManager",
75+
@"RCTTiming",
76+
@"RCTWebSocketModule",
77+
@"RCTNativeAnimatedModule",
78+
@"RCTNativeAnimatedTurboModule",
79+
@"RCTBlobManager",
80+
@"RCTFileReaderModule",
81+
@"RCTBundleAssetImageLoader",
82+
@"RCTGIFImageDecoder",
83+
@"RCTImageEditingManager",
84+
@"RCTImageLoader",
85+
@"RCTImageStoreManager",
86+
@"RCTImageViewManager",
87+
@"RCTLocalAssetImageLoader",
88+
@"RCTLinkingManager",
89+
@"RCTDataRequestHandler",
90+
@"RCTFileRequestHandler",
91+
@"RCTHTTPRequestHandler",
92+
@"RCTNetworking",
93+
@"RCTPushNotificationManager",
94+
@"RCTSettingsManager",
95+
@"RCTBaseTextViewManager",
96+
@"RCTBaseTextInputViewManager",
97+
@"RCTInputAccessoryViewManager",
98+
@"RCTMultilineTextInputViewManager",
99+
@"RCTRawTextViewManager",
100+
@"RCTSinglelineTextInputViewManager",
101+
@"RCTTextViewManager",
102+
@"RCTVirtualTextViewManager",
103+
@"RCTVibration",
104+
];
105+
// macOS]
106+
35107
static NSMutableArray<Class> *RCTModuleClasses;
36108
static dispatch_queue_t RCTModuleClassesSyncQueue;
109+
110+
// [macOS
111+
/**
112+
* Make sure ModuleClassesSyncQueue is initialized before any referring functions are called.
113+
*/
114+
static void RCTEnsureModuleClassesInitialized(void)
115+
{
116+
static dispatch_once_t onceToken;
117+
dispatch_once(&onceToken, ^{
118+
RCTModuleClasses = [NSMutableArray new];
119+
RCTModuleClassesSyncQueue =
120+
dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);
121+
});
122+
}
123+
124+
/**
125+
* Checks for unregistered modules that conform to RCTBridgeModule protocol.
126+
* This detects misconfiguration where external modules are compiled with
127+
* RCT_MODULE_NO_SELF_LOAD=1 but aren't in the moduleClassNames list.
128+
*/
129+
static void RCTCheckForUnregisteredModules(NSArray<Class> *registeredClasses)
130+
{
131+
static dispatch_once_t onceToken;
132+
dispatch_once(&onceToken, ^{
133+
NSMutableSet<Class> *registeredSet = [NSMutableSet setWithArray:registeredClasses];
134+
135+
// Get all loaded classes
136+
int numClasses = objc_getClassList(NULL, 0);
137+
if (numClasses <= 0) {
138+
return;
139+
}
140+
141+
Class *classes = (Class *)malloc(sizeof(Class) * numClasses);
142+
numClasses = objc_getClassList(classes, numClasses);
143+
144+
NSMutableArray<NSString *> *unregisteredModules = [NSMutableArray new];
145+
146+
// Check each class that conforms to RCTBridgeModule
147+
for (int i = 0; i < numClasses; i++) {
148+
Class cls = classes[i];
149+
150+
// Skip if already registered
151+
if ([registeredSet containsObject:cls]) {
152+
continue;
153+
}
154+
155+
// Check if class conforms to RCTBridgeModule protocol
156+
if (class_conformsToProtocol(cls, @protocol(RCTBridgeModule))) {
157+
// Skip if it's a core module that will be added
158+
NSString *className = NSStringFromClass(cls);
159+
if ([moduleClassNames containsObject:className]) {
160+
continue;
161+
}
162+
163+
[unregisteredModules addObject:className];
164+
}
165+
}
166+
167+
free(classes);
168+
169+
// Log warning if unregistered modules found
170+
if ([unregisteredModules count] > 0) {
171+
RCTLogWarn(@"⚠️ Detected unregistered RCTBridgeModule classes: %@\n"
172+
@"These modules may have been compiled with RCT_MODULE_NO_SELF_LOAD=1 "
173+
@"but are not in the core moduleClassNames list.\n"
174+
@"To fix: Either compile all modules with RCT_MODULE_NO_SELF_LOAD=0, "
175+
@"or add external modules to moduleClassNames in RCTBridge.mm",
176+
[unregisteredModules componentsJoinedByString:@", "]);
177+
}
178+
});
179+
}
180+
// macOS]
181+
37182
NSArray<Class> *RCTGetModuleClasses(void)
38183
{
184+
// [macOS
185+
RCTEnsureModuleClassesInitialized();
186+
187+
#if RCT_MODULE_NO_SELF_LOAD
188+
// When RCT_MODULE_NO_SELF_LOAD is enabled, modules don't self-register via +load
189+
// Add core React Native modules here instead
190+
__block NSMutableArray<Class> *result;
191+
dispatch_sync(RCTModuleClassesSyncQueue, ^{
192+
result = [RCTModuleClasses mutableCopy];
193+
});
194+
195+
for (NSString *className in moduleClassNames) {
196+
Class cls = NSClassFromString(className);
197+
if (cls != nil) {
198+
[result addObject:cls];
199+
}
200+
}
201+
202+
NSArray<Class> *finalResult = [result copy];
203+
204+
// Check for misconfigured external modules
205+
RCTCheckForUnregisteredModules(finalResult);
206+
207+
return finalResult;
208+
#else
209+
// macOS]
39210
__block NSArray<Class> *result;
40211
dispatch_sync(RCTModuleClassesSyncQueue, ^{
41212
result = [RCTModuleClasses copy];
42213
});
214+
215+
// [macOS
216+
// Check for misconfigured external modules
217+
RCTCheckForUnregisteredModules(result);
218+
// macOS]
219+
43220
return result;
221+
// [macOS
222+
#endif //RCT_MODULE_NO_SELF_LOAD
223+
// macOS]
44224
}
45225

46226
NSSet<NSString *> *getCoreModuleClasses(void);
@@ -49,69 +229,7 @@
49229
static NSSet<NSString *> *coreModuleClasses = nil;
50230
static dispatch_once_t onceToken;
51231
dispatch_once(&onceToken, ^{
52-
coreModuleClasses = [NSSet setWithArray:@[
53-
@"RCTViewManager",
54-
@"RCTActivityIndicatorViewManager",
55-
@"RCTDebuggingOverlayManager",
56-
@"RCTModalHostViewManager",
57-
@"RCTModalManager",
58-
@"RCTRefreshControlManager",
59-
@"RCTSafeAreaViewManager",
60-
@"RCTScrollContentViewManager",
61-
@"RCTScrollViewManager",
62-
@"RCTSwitchManager",
63-
@"RCTUIManager",
64-
@"RCTAccessibilityManager",
65-
@"RCTActionSheetManager",
66-
@"RCTAlertManager",
67-
@"RCTAppearance",
68-
@"RCTAppState",
69-
@"RCTClipboard",
70-
@"RCTDeviceInfo",
71-
@"RCTDevLoadingView",
72-
@"RCTDevMenu",
73-
@"RCTDevSettings",
74-
@"RCTDevToolsRuntimeSettingsModule",
75-
@"RCTEventDispatcher",
76-
@"RCTExceptionsManager",
77-
@"RCTI18nManager",
78-
@"RCTKeyboardObserver",
79-
@"RCTLogBox",
80-
@"RCTPerfMonitor",
81-
@"RCTPlatform",
82-
@"RCTRedBox",
83-
@"RCTSourceCode",
84-
@"RCTStatusBarManager",
85-
@"RCTTiming",
86-
@"RCTWebSocketModule",
87-
@"RCTNativeAnimatedModule",
88-
@"RCTNativeAnimatedTurboModule",
89-
@"RCTBlobManager",
90-
@"RCTFileReaderModule",
91-
@"RCTBundleAssetImageLoader",
92-
@"RCTGIFImageDecoder",
93-
@"RCTImageEditingManager",
94-
@"RCTImageLoader",
95-
@"RCTImageStoreManager",
96-
@"RCTImageViewManager",
97-
@"RCTLocalAssetImageLoader",
98-
@"RCTLinkingManager",
99-
@"RCTDataRequestHandler",
100-
@"RCTFileRequestHandler",
101-
@"RCTHTTPRequestHandler",
102-
@"RCTNetworking",
103-
@"RCTPushNotificationManager",
104-
@"RCTSettingsManager",
105-
@"RCTBaseTextViewManager",
106-
@"RCTBaseTextInputViewManager",
107-
@"RCTInputAccessoryViewManager",
108-
@"RCTMultilineTextInputViewManager",
109-
@"RCTRawTextViewManager",
110-
@"RCTSinglelineTextInputViewManager",
111-
@"RCTTextViewManager",
112-
@"RCTVirtualTextViewManager",
113-
@"RCTVibration",
114-
]];
232+
coreModuleClasses = [NSSet setWithArray:moduleClassNames]; // [macOS]
115233
});
116234

117235
return coreModuleClasses;
@@ -146,12 +264,8 @@ void RCTRegisterModule(Class moduleClass)
146264
![getCoreModuleClasses() containsObject:[moduleClass description]]) {
147265
addModuleLoadedWithOldArch([moduleClass description]);
148266
}
149-
static dispatch_once_t onceToken;
150-
dispatch_once(&onceToken, ^{
151-
RCTModuleClasses = [NSMutableArray new];
152-
RCTModuleClassesSyncQueue =
153-
dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);
154-
});
267+
268+
RCTEnsureModuleClassesInitialized(); // [macOS]
155269

156270
RCTAssert(
157271
[moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],

packages/react-native/React/Base/RCTBridgeModule.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ RCT_EXTERN_C_END
6363
*/
6464
@protocol RCTBridgeModule <NSObject>
6565

66+
// [macOS
67+
#if RCT_MODULE_NO_SELF_LOAD
68+
#define RCT_EXPORT_MODULE_LOAD
69+
#else
70+
#define RCT_EXPORT_MODULE_LOAD \
71+
+(void)load \
72+
{ \
73+
RCTRegisterModule(self); \
74+
}
75+
#endif
76+
// macOS]
77+
6678
/**
6779
* Place this macro in your class implementation to automatically register
6880
* your module with the bridge when it loads. The optional js_name argument
@@ -75,10 +87,7 @@ RCT_EXTERN_C_END
7587
{ \
7688
return @ #js_name; \
7789
} \
78-
+(void)load \
79-
{ \
80-
RCTRegisterModule(self); \
81-
}
90+
RCT_EXPORT_MODULE_LOAD // [macOS]
8291

8392
/**
8493
* Same as RCT_EXPORT_MODULE, but uses __attribute__((constructor)) for module

packages/react-native/React/Base/RCTDefines.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@
2222
#define RCT_EXTERN_C_END
2323
#endif
2424

25+
// [macOS
26+
/**
27+
* The RCT_MODULE_NO_SELF_LOAD macro can be used to disable module self-registration
28+
* via +load methods. When enabled, modules are registered by RCTBridge instead.
29+
* This can improve performance during module lazy-loading.
30+
*/
31+
#ifndef RCT_MODULE_NO_SELF_LOAD
32+
#define RCT_MODULE_NO_SELF_LOAD 0
33+
#endif
34+
// macOS]
35+
2536
/**
2637
* The RCT_DEBUG macro can be used to exclude error checking and logging code
2738
* from release builds to improve performance and reduce binary size.

0 commit comments

Comments
 (0)