diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 3e31621d0c..1ab00bb5cf 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -263,6 +263,19 @@ RCT_EXTERN void RCTRegisterModule(Class); \ return &config; \ } +/** + * Most modules can be used from any thread. All of the modules exported non-sync method will be called on its + * methodQueue, and the module will be constructed lazily when its first invoked. Some modules have main need to access + * information that's main queue only (e.g. most UIKit classes). Since we don't want to dispatch synchronously to the + * main thread to this safely, we construct these moduels and export their constants ahead-of-time. + * + * Note that when set to false, the module constructor will be called from any thread. + * + * This requirement is currently inferred by checking if the module has a custom initializer or if there's exported + * constants. In the future, we'll stop automatically inferring this and instead only rely on this method. + */ ++ (BOOL)requiresMainQueueSetup; + /** * Injects methods into JS. Entries in this array are used in addition to any * methods defined using the macros above. This method is called only once, @@ -271,13 +284,15 @@ RCT_EXTERN void RCTRegisterModule(Class); \ - (NSArray> *)methodsToExport; /** - * Injects constants into JS. These constants are made accessible via - * NativeModules.ModuleName.X. It is only called once for the lifetime of the - * bridge, so it is not suitable for returning dynamic values, but may be used - * for long-lived values such as session keys, that are regenerated only as - * part of a reload of the entire React application. + * Injects constants into JS. These constants are made accessible via NativeModules.ModuleName.X. It is only called once + * for the lifetime of the bridge, so it is not suitable for returning dynamic values, but may be used for long-lived + * values such as session keys, that are regenerated only as part of a reload of the entire React application. + * + * If you implement this method and do not implement `requiresMainThreadSetup`, you will trigger deprecated logic + * that eagerly initializes your module on bridge startup. In the future, this behaviour will be changed to default + * to initializing lazily, and even modules with constants will be initialized lazily. */ -- (NSDictionary *)constantsToExport; +- (NSDictionary *)constantsToExport; /** * Notifies the module that a batch of JS method invocations has just completed. diff --git a/React/Base/RCTModuleData.mm b/React/Base/RCTModuleData.mm index ae728a2c55..c1645549b3 100644 --- a/React/Base/RCTModuleData.mm +++ b/React/Base/RCTModuleData.mm @@ -38,21 +38,37 @@ _implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)]; _implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector(partialBatchDidFlush)]; - static IMP objectInitMethod; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)]; - }); - - // If a module overrides `constantsToExport` then we must assume that it - // must be called on the main thread, because it may need to access UIKit. + // If a module overrides `constantsToExport` and doesn't implement `requiresMainQueueSetup`, then we must assume + // that it must be called on the main thread, because it may need to access UIKit. _hasConstantsToExport = [_moduleClass instancesRespondToSelector:@selector(constantsToExport)]; - // If a module overrides `init` then we must assume that it expects to be - // initialized on the main thread, because it may need to access UIKit. - const BOOL hasCustomInit = !_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod; + const BOOL implementsRequireMainQueueSetup = [_moduleClass respondsToSelector:@selector(requiresMainQueueSetup)]; + if (implementsRequireMainQueueSetup) { + _requiresMainQueueSetup = [_moduleClass requiresMainQueueSetup]; + } else { + static IMP objectInitMethod; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)]; + }); - _requiresMainQueueSetup = _hasConstantsToExport || hasCustomInit; + // If a module overrides `init` then we must assume that it expects to be + // initialized on the main thread, because it may need to access UIKit. + const BOOL hasCustomInit = !_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod; + + _requiresMainQueueSetup = _hasConstantsToExport || hasCustomInit; + if (_requiresMainQueueSetup) { + const char *methodName = ""; + if (_hasConstantsToExport) { + methodName = "constantsToExport"; + } else if (hasCustomInit) { + methodName = "init"; + } + RCTLogWarn(@"Module %@ requires main queue setup since it overrides `%s` but doesn't implement " + "`requiresMainQueueSetup. In a future release React Native will default to initializing all native modules " + "on a background thread unless explicitly opted-out of.", _moduleClass, methodName); + } + } } - (instancetype)initWithModuleClass:(Class)moduleClass @@ -291,9 +307,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); if (_hasConstantsToExport && !_constantsToExport) { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass]), nil); (void)[self instance]; - if (!_requiresMainQueueSetup) { - _constantsToExport = [_instance constantsToExport] ?: @{}; - } else { + if (_requiresMainQueueSetup) { if (!RCTIsMainQueue()) { RCTLogWarn(@"Required dispatch_sync to load constants for %@. This may lead to deadlocks", _moduleClass); } @@ -301,6 +315,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); RCTUnsafeExecuteOnMainQueueSync(^{ self->_constantsToExport = [self->_instance constantsToExport] ?: @{}; }); + } else { + _constantsToExport = [_instance constantsToExport] ?: @{}; } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } diff --git a/React/CxxModule/RCTCxxModule.mm b/React/CxxModule/RCTCxxModule.mm index 3838cb7bcc..295aac82c2 100644 --- a/React/CxxModule/RCTCxxModule.mm +++ b/React/CxxModule/RCTCxxModule.mm @@ -28,6 +28,11 @@ using namespace facebook::react; return @""; } ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + - (void)lazyInit { if (!_module) {