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
This commit is contained in:
Nick Lockwood 2016-03-07 09:30:20 -08:00 committed by Facebook Github Bot 3
parent 7dbba3ba5f
commit 006907bdaa
6 changed files with 63 additions and 36 deletions

View File

@ -67,7 +67,6 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
RCTAssertMainThread();
RCTAssertParam(bridge);
if ((self = [super initWithBundleURL:bridge.bundleURL
@ -222,6 +221,11 @@ RCT_EXTERN NSArray<Class> *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<Class> *RCTGetModuleClasses(void);
- (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
RCTAssertMainThread();
RCTPerformanceLoggerStart(RCTPLNativeModuleInit);
NSArray<id<RCTBridgeModule>> *extraModules = nil;
@ -356,26 +359,39 @@ RCT_EXTERN NSArray<Class> *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);
}

View File

@ -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.
*/

View File

@ -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<RCTBridgeDelegate>)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];

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;
}