From 006907bdaa6975fc65480f8bf6867f1b97a3b2c4 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 7 Mar 2016 09:30:20 -0800 Subject: [PATCH] Initialize bridge on a background queue Summary: This diff adds support for initializing the bridge on an arbitrary thread. This is helpful if you want to defer bridge creation, or prevent it from delaying your app startup. Reviewed By: javache Differential Revision: D2965725 fb-gh-sync-id: 8065fa89e850031c72ee4427351300986985e9de shipit-source-id: 8065fa89e850031c72ee4427351300986985e9de --- React/Base/RCTBatchedBridge.m | 30 +++++++++++++++++++++++------- React/Base/RCTBridge.h | 7 +++++++ React/Base/RCTBridge.m | 18 +++++++++--------- React/Base/RCTModuleData.h | 5 +++++ React/Base/RCTModuleData.m | 35 +++++++++++++++++------------------ React/Views/RCTViewManager.m | 4 ++-- 6 files changed, 63 insertions(+), 36 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 3154e4813..9547262cb 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 1d8548607..9666b2cbe 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 cd3edd45f..bd3cf74ef 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 a851b2a38..7bd297739 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 b3569db11..bf2f7f846 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 88368290a..eef814a2b 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; }