diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 3154e4813c7..9547262cb92 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -67,7 +67,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); - (instancetype)initWithParentBridge:(RCTBridge *)bridge { - RCTAssertMainThread(); RCTAssertParam(bridge); if ((self = [super initWithBundleURL:bridge.bundleURL @@ -222,6 +221,11 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); return _moduleDataByName[moduleName].instance; } +- (BOOL)moduleIsInitialized:(Class)moduleClass +{ + return _moduleDataByName[RCTBridgeModuleNameForClass(moduleClass)].hasInstance; +} + - (NSArray *)configForModuleName:(NSString *)moduleName { RCTModuleData *moduleData = _moduleDataByName[moduleName]; @@ -236,7 +240,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); - (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup { - RCTAssertMainThread(); RCTPerformanceLoggerStart(RCTPLNativeModuleInit); NSArray> *extraModules = nil; @@ -356,26 +359,39 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); // trigger the lazy initialization process. (void)[moduleData instance]; } + } + + // From this point on, RCTDidInitializeModuleNotification notifications will + // be sent the first time a module is accessed. + _moduleSetupComplete = YES; + + // Set up modules that require main thread init or constants export + for (RCTModuleData *moduleData in _moduleDataByID) { + __weak RCTBatchedBridge *weakSelf = self; if (moduleData.requiresMainThreadSetup) { // Modules that need to be set up on the main thread cannot be initialized // lazily when required without doing a dispatch_sync to the main thread, // which can result in deadlock. To avoid this, we initialize all of these // modules on the main thread in parallel with loading the JS code, so that // they will already be available before they are ever required. - __weak RCTBatchedBridge *weakSelf = self; dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{ if (weakSelf.valid) { (void)[moduleData instance]; [moduleData gatherConstants]; } }); + } else if (moduleData.hasConstantsToExport) { + // Constants must be exported on the main thread, but module setup can + // be done on any queue + (void)[moduleData instance]; + dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{ + if (weakSelf.valid) { + [moduleData gatherConstants]; + } + }); } } - // From this point on, RCTDidInitializeModuleNotification notifications will - // be sent the first time a module is accessed. - _moduleSetupComplete = YES; - RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); } diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 1d8548607e2..9666b2cbeed 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -122,6 +122,13 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class); */ - (NSArray *)modulesConformingToProtocol:(Protocol *)protocol; +/** + * Test if a module has been initialized. Use this prior to calling + * `moduleForClass:` or `moduleForName:` if you do not want to cause the module + * to be instantiated if it hasn't been already. + */ +- (BOOL)moduleIsInitialized:(Class)moduleClass; + /** * All registered bridge module classes. */ diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index cd3edd45fd6..bd3cf74ef41 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -15,6 +15,7 @@ #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" +#import "RCTModuleData.h" #import "RCTPerformanceLogger.h" #import "RCTUtils.h" @@ -128,15 +129,13 @@ static RCTBridge *RCTCurrentBridgeInstance = nil; - (instancetype)initWithDelegate:(id)delegate launchOptions:(NSDictionary *)launchOptions { - RCTAssertMainThread(); - if ((self = [super init])) { RCTPerformanceLoggerStart(RCTPLTTI); _delegate = delegate; _launchOptions = [launchOptions copy]; [self setUp]; - [self bindKeys]; + RCTExecuteOnMainThread(^{ [self bindKeys]; }, NO); } return self; } @@ -145,8 +144,6 @@ static RCTBridge *RCTCurrentBridgeInstance = nil; moduleProvider:(RCTBridgeModuleProviderBlock)block launchOptions:(NSDictionary *)launchOptions { - RCTAssertMainThread(); - if ((self = [super init])) { RCTPerformanceLoggerStart(RCTPLTTI); @@ -154,7 +151,7 @@ static RCTBridge *RCTCurrentBridgeInstance = nil; _moduleProvider = block; _launchOptions = [launchOptions copy]; [self setUp]; - [self bindKeys]; + RCTExecuteOnMainThread(^{ [self bindKeys]; }, NO); } return self; } @@ -224,6 +221,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) return [modules copy]; } +- (BOOL)moduleIsInitialized:(Class)moduleClass +{ + return [self.batchedBridge moduleIsInitialized:moduleClass]; +} + - (RCTEventDispatcher *)eventDispatcher { return [self moduleForClass:[RCTEventDispatcher class]]; @@ -232,7 +234,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (void)reload { /** - * AnyThread + * Any thread */ dispatch_async(dispatch_get_main_queue(), ^{ [self invalidate]; @@ -242,8 +244,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (void)setUp { - RCTAssertMainThread(); - // Only update bundleURL from delegate if delegate bundleURL has changed NSURL *previousDelegateURL = _delegateBundleURL; _delegateBundleURL = [self.delegate sourceURLForBridge:self]; diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h index a851b2a387b..7bd297739f6 100644 --- a/React/Base/RCTModuleData.h +++ b/React/Base/RCTModuleData.h @@ -50,6 +50,11 @@ */ @property (nonatomic, assign, readonly) BOOL requiresMainThreadSetup; +/** + * Returns YES if module has constants to export. + */ +@property (nonatomic, assign, readonly) BOOL hasConstantsToExport; + /** * Returns the current module instance. Note that this will init the instance * if it has not already been created. To check if the module instance exists diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index b3569db110e..bf2f7f84613 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -43,13 +43,16 @@ setBridgeSelector = NSSelectorFromString(@"setBridge:"); }); - // If a module overrides `init`, `setBridge:`, or `constantsToExport` then we - // must assume that it expects for both of those methods to be called on the - // main thread, because those methods often need to access UIKit. - _requiresMainThreadSetup = _instance || + // If a module overrides `init`, `setBridge:` then we must assume that it + // expects for both of those methods to be called on the main thread, because + // they may need to access UIKit. + _requiresMainThreadSetup = [_moduleClass instancesRespondToSelector:setBridgeSelector] || - RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport)) || - [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod; + (!_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod); + + // 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. + _hasConstantsToExport = RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport)); } - (instancetype)initWithModuleClass:(Class)moduleClass @@ -145,12 +148,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); - (void)setUpMethodQueue { - if (_instance && !_methodQueue) { + if (_instance && !_methodQueue && _bridge.valid) { BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)]; - if (implementsMethodQueue) { + if (implementsMethodQueue && _bridge.valid) { _methodQueue = _instance.methodQueue; } - if (!_methodQueue) { + if (!_methodQueue && _bridge.valid) { // Create new queue (store queueName, as it isn't retained by dispatch_queue) _queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", self.name]; @@ -240,15 +243,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); - (void)gatherConstants { - if (!_constantsToExport) { - if (RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport))) { - RCTExecuteOnMainThread(^{ - [self setUpInstanceAndBridge]; - _constantsToExport = [_instance constantsToExport] ?: @{}; - }, YES); - } else { - _constantsToExport = @{}; - } + if (_hasConstantsToExport && !_constantsToExport) { + (void)[self instance]; + RCTExecuteOnMainThread(^{ + _constantsToExport = [_instance constantsToExport] ?: @{}; + }, YES); } } diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 88368290acf..eef814a2bf7 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -53,8 +53,8 @@ RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue { RCTAssert(_bridge, @"Bridge not set"); - RCTAssert(_bridge.uiManager, @"UIManager not initialized"); - RCTAssert(_bridge.uiManager.methodQueue, @"UIManager.methodQueue not initialized"); + RCTAssert(_bridge.uiManager || !_bridge.valid, @"UIManager not initialized"); + RCTAssert(_bridge.uiManager.methodQueue || !_bridge.valid, @"UIManager.methodQueue not initialized"); return _bridge.uiManager.methodQueue; }