Skip to content

Commit cf38b08

Browse files
Dmitry Zakharovfacebook-github-bot
authored andcommitted
Implement lazy discovery scaffolding for loading NativeModules on demand.
Reviewed By: javache Differential Revision: D5364734 fbshipit-source-id: 5162f7d41434a3ba38c82fa610e84f865bfacf50
1 parent 21b1ed3 commit cf38b08

File tree

6 files changed

+169
-93
lines changed

6 files changed

+169
-93
lines changed

React/Base/RCTBridge+Private.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ RCT_EXTERN void RCTVerifyAllModulesExported(NSArray *extraModules);
114114
*/
115115
- (RCTModuleData *)moduleDataForName:(NSString *)moduleName;
116116

117+
/**
118+
* Registers additional classes with the ModuleRegistry.
119+
*/
120+
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)newModules;
121+
117122
/**
118123
* Systrace profiler toggling methods exposed for the RCTDevMenu
119124
*/

React/Base/RCTBridge.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,11 @@ - (void)invalidate
353353
}
354354
}
355355

356+
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)modules
357+
{
358+
[self.batchedBridge registerAdditionalModuleClasses:modules];
359+
}
360+
356361
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
357362
{
358363
NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."];

React/Base/RCTBridgeDelegate.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@
105105
*/
106106
- (BOOL)shouldBridgeUseCxxBridge:(RCTBridge *)bridge;
107107

108+
/**
109+
* The bridge will call this method when a module been called from JS
110+
* cannot be found among registered modules.
111+
* It should return YES if the module with name 'moduleName' was registered
112+
* in the implementation, and the system must attempt to look for it again among registered.
113+
* If the module was not registered, return NO to prevent further searches.
114+
*/
115+
- (BOOL)bridge:(RCTBridge *)bridge didNotFindModule:(NSString *)moduleName;
116+
108117
/**
109118
* The bridge will automatically attempt to load the JS source code from the
110119
* location specified by the `sourceURLForBridge:` method, however, if you want

React/CxxBridge/RCTCxxBridge.mm

Lines changed: 126 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ @implementation RCTCxxBridge
138138

139139
// Native modules
140140
NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
141-
NSArray<RCTModuleData *> *_moduleDataByID;
142-
NSArray<Class> *_moduleClassesByID;
141+
NSMutableArray<RCTModuleData *> *_moduleDataByID;
142+
NSMutableArray<Class> *_moduleClassesByID;
143143
NSUInteger _modulesInitializedOnMainQueue;
144144
RCTDisplayLink *_displayLink;
145145

@@ -195,6 +195,10 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge
195195
_pendingCalls = [NSMutableArray new];
196196
_displayLink = [RCTDisplayLink new];
197197

198+
_moduleDataByName = [NSMutableDictionary new];
199+
_moduleClassesByID = [NSMutableArray new];
200+
_moduleDataByID = [NSMutableArray new];
201+
198202
[RCTBridge setCurrentBridge:self];
199203
}
200204
return self;
@@ -275,8 +279,13 @@ - (void)start
275279

276280
dispatch_group_t prepareBridge = dispatch_group_create();
277281

282+
[_performanceLogger markStartForTag:RCTPLNativeModuleInit];
283+
284+
[self registerExtraModules];
278285
// Initialize all native modules that cannot be loaded lazily
279-
[self _initModulesWithDispatchGroup:prepareBridge];
286+
[self _initModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
287+
288+
[_performanceLogger markStopForTag:RCTPLNativeModuleInit];
280289

281290
// This doesn't really do anything. The real work happens in initializeBridge.
282291
_reactInstance.reset(new Instance);
@@ -442,7 +451,16 @@ - (BOOL)moduleIsInitialized:(Class)moduleClass
442451
[_performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig];
443452
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge buildModuleRegistry]", nil);
444453

445-
auto registry = std::make_shared<ModuleRegistry>(createNativeModules(_moduleDataByID, self, _reactInstance));
454+
__weak __typeof(self) weakSelf = self;
455+
ModuleRegistry::ModuleNotFoundCallback moduleNotFoundCallback = ^bool(const std::string &name) {
456+
__strong __typeof(weakSelf) strongSelf = weakSelf;
457+
return [strongSelf.delegate respondsToSelector:@selector(bridge:didNotFindModule:)] &&
458+
[strongSelf.delegate bridge:strongSelf didNotFindModule:@(name.c_str())];
459+
};
460+
461+
auto registry = std::make_shared<ModuleRegistry>(
462+
createNativeModules(_moduleDataByID, self, _reactInstance),
463+
moduleNotFoundCallback);
446464

447465
[_performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig];
448466
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
@@ -501,20 +519,66 @@ - (NSArray *)configForModuleName:(NSString *)moduleName
501519
return moduleData.config;
502520
}
503521

504-
- (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
522+
- (NSArray<RCTModuleData *> *)registerModulesForClasses:(NSArray<Class> *)moduleClasses
505523
{
506-
[_performanceLogger markStartForTag:RCTPLNativeModuleInit];
524+
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
525+
@"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil);
526+
527+
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClasses.count];
528+
for (Class moduleClass in moduleClasses) {
529+
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
530+
531+
// Don't initialize the old executor in the new bridge.
532+
// TODO mhorowitz #10487027: after D3175632 lands, we won't need
533+
// this, because it won't be eagerly initialized.
534+
if ([moduleName isEqualToString:@"RCTJSCExecutor"]) {
535+
continue;
536+
}
537+
538+
// Check for module name collisions
539+
RCTModuleData *moduleData = _moduleDataByName[moduleName];
540+
if (moduleData) {
541+
if (moduleData.hasInstance) {
542+
// Existing module was preregistered, so it takes precedence
543+
continue;
544+
} else if ([moduleClass new] == nil) {
545+
// The new module returned nil from init, so use the old module
546+
continue;
547+
} else if ([moduleData.moduleClass new] != nil) {
548+
// Both modules were non-nil, so it's unclear which should take precedence
549+
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
550+
"name '%@', but name was already registered by class %@",
551+
moduleClass, moduleName, moduleData.moduleClass);
552+
}
553+
}
554+
555+
// Instantiate moduleData
556+
// TODO #13258411: can we defer this until config generation?
557+
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self];
507558

559+
_moduleDataByName[moduleName] = moduleData;
560+
[_moduleClassesByID addObject:moduleClass];
561+
[moduleDataByID addObject:moduleData];
562+
}
563+
[_moduleDataByID addObjectsFromArray:moduleDataByID];
564+
565+
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
566+
567+
return moduleDataByID;
568+
}
569+
570+
- (void)registerExtraModules
571+
{
508572
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
509573
@"-[RCTCxxBridge initModulesWithDispatchGroup:] extraModules", nil);
574+
510575
NSArray<id<RCTBridgeModule>> *extraModules = nil;
511-
if (self.delegate) {
512-
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
513-
extraModules = [self.delegate extraModulesForBridge:_parentBridge];
514-
}
576+
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
577+
extraModules = [self.delegate extraModulesForBridge:_parentBridge];
515578
} else if (self.moduleProvider) {
516579
extraModules = self.moduleProvider();
517580
}
581+
518582
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
519583

520584
#if RCT_DEBUG
@@ -524,10 +588,6 @@ - (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
524588
});
525589
#endif
526590

527-
NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
528-
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
529-
NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
530-
531591
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
532592
@"-[RCTCxxBridge initModulesWithDispatchGroup:] preinitialized moduleData", nil);
533593
// Set up moduleData for pre-initialized module instances
@@ -537,7 +597,7 @@ - (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
537597

538598
if (RCT_DEBUG) {
539599
// Check for name collisions between preregistered modules
540-
RCTModuleData *moduleData = moduleDataByName[moduleName];
600+
RCTModuleData *moduleData = _moduleDataByName[moduleName];
541601
if (moduleData) {
542602
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
543603
"name '%@', but name was already registered by class %@",
@@ -547,84 +607,60 @@ - (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
547607
}
548608

549609
// Instantiate moduleData container
550-
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
551-
bridge:self];
552-
moduleDataByName[moduleName] = moduleData;
553-
[moduleClassesByID addObject:moduleClass];
554-
[moduleDataByID addObject:moduleData];
610+
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module bridge:self];
611+
_moduleDataByName[moduleName] = moduleData;
612+
[_moduleClassesByID addObject:moduleClass];
613+
[_moduleDataByID addObject:moduleData];
555614
}
556615
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
616+
}
557617

558-
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
559-
@"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil);
560-
// Set up moduleData for automatically-exported modules
561-
for (Class moduleClass in RCTGetModuleClasses()) {
562-
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
618+
- (void)_initModules:(NSArray<id<RCTBridgeModule>> *)modules
619+
withDispatchGroup:(dispatch_group_t)dispatchGroup
620+
lazilyDiscovered:(BOOL)lazilyDiscovered
621+
{
622+
RCTAssert(!(RCTIsMainQueue() && lazilyDiscovered), @"Lazy discovery can only happen off the Main Queue");
563623

564-
// Don't initialize the old executor in the new bridge.
565-
// TODO mhorowitz #10487027: after D3175632 lands, we won't need
566-
// this, because it won't be eagerly initialized.
567-
if ([moduleName isEqual:@"RCTJSCExecutor"]) {
568-
continue;
624+
// Set up moduleData for automatically-exported modules
625+
NSArray<RCTModuleData *> *moduleDataById = [self registerModulesForClasses:modules];
626+
627+
#ifdef RCT_DEBUG
628+
if (lazilyDiscovered) {
629+
// Lazily discovered modules do not require instantiation here,
630+
// as they are not allowed to have pre-instantiated instance
631+
// and must not require the main queue.
632+
for (RCTModuleData *moduleData in moduleDataById) {
633+
RCTAssert(!(moduleData.requiresMainQueueSetup || moduleData.hasInstance),
634+
@"Module \'%@\' requires initialization on the Main Queue or has pre-instantiated, which is not supported for the lazily discovered modules.", moduleData.name);
569635
}
570-
571-
// Check for module name collisions
572-
RCTModuleData *moduleData = moduleDataByName[moduleName];
573-
if (moduleData) {
574-
if (moduleData.hasInstance) {
575-
// Existing module was preregistered, so it takes precedence
576-
continue;
577-
} else if ([moduleClass new] == nil) {
578-
// The new module returned nil from init, so use the old module
579-
continue;
580-
} else if ([moduleData.moduleClass new] != nil) {
581-
// Both modules were non-nil, so it's unclear which should take precedence
582-
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
583-
"name '%@', but name was already registered by class %@",
584-
moduleClass, moduleName, moduleData.moduleClass);
636+
}
637+
else
638+
#endif
639+
{
640+
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
641+
@"-[RCTCxxBridge initModulesWithDispatchGroup:] moduleData.hasInstance", nil);
642+
// Dispatch module init onto main thread for those modules that require it
643+
// For non-lazily discovered modules we run through the entire set of modules
644+
// that we have, otherwise some modules coming from the delegate
645+
// or module provider block, will not be properly instantiated.
646+
for (RCTModuleData *moduleData in _moduleDataByID) {
647+
if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
648+
// Modules that were pre-initialized should ideally be set up before
649+
// bridge init has finished, otherwise the caller may try to access the
650+
// module directly rather than via `[bridge moduleForClass:]`, which won't
651+
// trigger the lazy initialization process. If the module cannot safely be
652+
// set up on the current thread, it will instead be async dispatched
653+
// to the main thread to be set up in _prepareModulesWithDispatchGroup:.
654+
(void)[moduleData instance];
585655
}
586656
}
657+
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
587658

588-
// Instantiate moduleData
589-
// TODO #13258411: can we defer this until config generation?
590-
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
591-
bridge:self];
592-
moduleDataByName[moduleName] = moduleData;
593-
[moduleClassesByID addObject:moduleClass];
594-
[moduleDataByID addObject:moduleData];
595-
}
596-
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
597-
598-
// Store modules
599-
_moduleDataByID = [moduleDataByID copy];
600-
_moduleDataByName = [moduleDataByName copy];
601-
_moduleClassesByID = [moduleClassesByID copy];
602-
603-
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
604-
@"-[RCTCxxBridge initModulesWithDispatchGroup:] moduleData.hasInstance", nil);
605-
// Dispatch module init onto main thead for those modules that require it
606-
for (RCTModuleData *moduleData in _moduleDataByID) {
607-
if (moduleData.hasInstance &&
608-
(!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
609-
// Modules that were pre-initialized should ideally be set up before
610-
// bridge init has finished, otherwise the caller may try to access the
611-
// module directly rather than via `[bridge moduleForClass:]`, which won't
612-
// trigger the lazy initialization process. If the module cannot safely be
613-
// set up on the current thread, it will instead be async dispatched
614-
// to the main thread to be set up in the loop below.
615-
(void)[moduleData instance];
616-
}
659+
// From this point on, RCTDidInitializeModuleNotification notifications will
660+
// be sent the first time a module is accessed.
661+
_moduleSetupComplete = YES;
662+
[self _prepareModulesWithDispatchGroup:dispatchGroup];
617663
}
618-
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
619-
620-
// From this point on, RCTDidInitializeModuleNotification notifications will
621-
// be sent the first time a module is accessed.
622-
_moduleSetupComplete = YES;
623-
624-
[self _prepareModulesWithDispatchGroup:dispatchGroup];
625-
626-
[_performanceLogger markStopForTag:RCTPLNativeModuleInit];
627-
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
628664

629665
#if RCT_PROFILE
630666
if (RCTProfileIsProfiling()) {
@@ -634,6 +670,11 @@ - (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
634670
#endif
635671
}
636672

673+
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)modules
674+
{
675+
[self _initModules:modules withDispatchGroup:NULL lazilyDiscovered:YES];
676+
}
677+
637678
- (void)_prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
638679
{
639680
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge prepareModulesWithDispatch]", nil);
@@ -655,6 +696,7 @@ - (void)_prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
655696

656697
// Set up modules that require main thread init or constants export
657698
[_performanceLogger setValue:0 forTag:RCTPLNativeModuleMainThread];
699+
658700
for (RCTModuleData *moduleData in _moduleDataByID) {
659701
if (whitelistedModules && ![whitelistedModules containsObject:[moduleData moduleClass]]) {
660702
continue;
@@ -687,7 +729,6 @@ - (void)_prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
687729
_modulesInitializedOnMainQueue++;
688730
}
689731
}
690-
691732
[_performanceLogger setValue:_modulesInitializedOnMainQueue forTag:RCTPLNativeModuleMainThreadUsesCount];
692733
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
693734
}

ReactCommon/cxxreact/ModuleRegistry.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ std::string normalizeName(std::string name) {
2727

2828
}
2929

30-
ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules)
31-
: modules_(std::move(modules)) {}
30+
ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules, ModuleNotFoundCallback callback)
31+
: modules_{std::move(modules)}, moduleNotFoundCallback_{callback} {}
3232

3333
void ModuleRegistry::updateModuleNamesFromIndex(size_t index) {
3434
for (; index < modules_.size(); index++ ) {
@@ -82,13 +82,22 @@ folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name)
8282
}
8383

8484
auto it = modulesByName_.find(name);
85+
8586
if (it == modulesByName_.end()) {
86-
unknownModules_.insert(name);
87-
return nullptr;
87+
if (unknownModules_.find(name) != unknownModules_.end()) {
88+
return nullptr;
89+
}
90+
if (!moduleNotFoundCallback_ ||
91+
!moduleNotFoundCallback_(name) ||
92+
(it = modulesByName_.find(name)) == modulesByName_.end()) {
93+
unknownModules_.insert(name);
94+
return nullptr;
95+
}
8896
}
97+
size_t index = it->second;
8998

90-
CHECK(it->second < modules_.size());
91-
NativeModule* module = modules_[it->second].get();
99+
CHECK(index < modules_.size());
100+
NativeModule *module = modules_[index].get();
92101

93102
// string name, object constants, array methodNames (methodId is index), [array promiseMethodIds], [array syncMethodIds]
94103
folly::dynamic config = folly::dynamic::array(name);
@@ -131,7 +140,7 @@ folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name)
131140
// no constants or methods
132141
return nullptr;
133142
} else {
134-
return ModuleConfig({it->second, config});
143+
return ModuleConfig{index, config};
135144
}
136145
}
137146

0 commit comments

Comments
 (0)