Dispatch module setup asynchronously to avoid blocking main thread when bridge starts

Summary:Initializing native modules can block the main thread for tens of milliseconds when it starts up, making it difficult to instantiate the bridge on demand without causing a performance blip.

This diff splits up the initialization of modules so that - although they still happen on the main thread - they don't block the thread continuously.

Reviewed By: javache

Differential Revision: D2965438

fb-gh-sync-id: 38c9c9d281e4672b5874d68b57d4c60d1d268344
shipit-source-id: 38c9c9d281e4672b5874d68b57d4c60d1d268344
This commit is contained in:
Nick Lockwood 2016-03-03 02:20:20 -08:00 committed by Facebook Github Bot 6
parent 8f3e5b1e93
commit dc13115445
13 changed files with 311 additions and 318 deletions

View File

@ -47,10 +47,9 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
return nil;
}];
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader]; } launchOptions:nil];
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
[bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
XCTAssertEqual(progress, 1);
XCTAssertEqual(total, 1);
} completionBlock:^(NSError *loadError, id loadedImage) {
@ -78,10 +77,9 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
return nil;
}];
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil];
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
[bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
XCTAssertEqual(progress, 1);
XCTAssertEqual(total, 1);
} completionBlock:^(NSError *loadError, id loadedImage) {
@ -103,10 +101,9 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
return nil;
}];
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder]; } launchOptions:nil];
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
XCTAssertEqualObjects(decodedImage, image);
XCTAssertNil(decodeError);
}];
@ -133,10 +130,9 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
return nil;
}];
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2]; } launchOptions:nil];
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
XCTAssertEqualObjects(decodedImage, image);
XCTAssertNil(decodeError);
}];

View File

@ -34,26 +34,13 @@
} \
}
// Must be declared before RCTTestCustomSetBridgeModule in order to trigger the
// race condition that we are testing for - namely that the
// RCTDidInitializeModuleNotification for RCTTestViewManager gets sent before
// setBridge: is called on RCTTestCustomSetBridgeModule
@interface RCTTestViewManager : RCTViewManager
@end
@implementation RCTTestViewManager
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
(void)[_bridge uiManager]; // Needed to trigger a race condition
}
- (NSArray<NSString *> *)customDirectEventTypes
{
return @[@"foo"];
@ -112,7 +99,7 @@ RCT_EXPORT_MODULE()
- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge
{
return @[_notificationObserver];
return @[[RCTTestViewManager new], _notificationObserver];
}
- (void)setUp

View File

@ -202,10 +202,11 @@ RCT_EXPORT_MODULE()
- (void)testInjectedModulesInitializedDuringBridgeInit
{
XCTAssertTrue(_injectedModuleInitNotificationSent);
XCTAssertEqual(_injectedModule, [_bridge moduleForClass:[RCTTestInjectedModule class]]);
XCTAssertEqual(_injectedModule.bridge, _bridge.batchedBridge);
XCTAssertNotNil(_injectedModule.methodQueue);
RUN_RUNLOOP_WHILE(!_injectedModuleInitNotificationSent);
XCTAssertTrue(_injectedModuleInitNotificationSent);
}
- (void)testCustomInitModuleInitializedAtBridgeStartup
@ -214,6 +215,8 @@ RCT_EXPORT_MODULE()
XCTAssertTrue(_customInitModuleNotificationSent);
RCTTestCustomInitModule *module = [_bridge moduleForClass:[RCTTestCustomInitModule class]];
XCTAssertTrue(module.initializedOnMainThread);
XCTAssertEqual(module.bridge, _bridge.batchedBridge);
XCTAssertNotNil(module.methodQueue);
}
- (void)testCustomSetBridgeModuleInitializedAtBridgeStartup
@ -222,6 +225,8 @@ RCT_EXPORT_MODULE()
XCTAssertTrue(_customSetBridgeModuleNotificationSent);
RCTTestCustomSetBridgeModule *module = [_bridge moduleForClass:[RCTTestCustomSetBridgeModule class]];
XCTAssertTrue(module.setBridgeOnMainThread);
XCTAssertEqual(module.bridge, _bridge.batchedBridge);
XCTAssertNotNil(module.methodQueue);
}
- (void)testExportConstantsModuleInitializedAtBridgeStartup
@ -232,6 +237,8 @@ RCT_EXPORT_MODULE()
RUN_RUNLOOP_WHILE(!module.exportedConstants);
XCTAssertTrue(module.exportedConstants);
XCTAssertTrue(module.exportedConstantsOnMainThread);
XCTAssertEqual(module.bridge, _bridge.batchedBridge);
XCTAssertNotNil(module.methodQueue);
}
- (void)testLazyInitModuleNotInitializedDuringBridgeInit

View File

@ -91,6 +91,15 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
resizeMode:(RCTResizeMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
/**
* Decodes an image without clipping the result to fit.
*/
- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
/**
* Get image size, in pixels. This method will do the least work possible to get
* the information, and won't decode the image if it doesn't have to.

View File

@ -59,20 +59,19 @@ RCT_EXPORT_MODULE()
_maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
_maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 *1024; // 30MB
// Get image loaders and decoders
NSMutableArray<id<RCTImageURLLoader>> *loaders = [NSMutableArray array];
NSMutableArray<id<RCTImageDataDecoder>> *decoders = [NSMutableArray array];
for (Class moduleClass in _bridge.moduleClasses) {
if ([moduleClass conformsToProtocol:@protocol(RCTImageURLLoader)]) {
[loaders addObject:[_bridge moduleForClass:moduleClass]];
}
if ([moduleClass conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
[decoders addObject:[_bridge moduleForClass:moduleClass]];
}
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
}
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
{
if (!_maxConcurrentLoadingTasks) {
[self setUp];
}
// Sort loaders in reverse priority order (highest priority first)
[loaders sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
if (!_loaders) {
// Get loaders, sorted in reverse priority order (highest priority first)
RCTAssert(_bridge, @"Bridge not set");
_loaders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
if (priorityA > priorityB) {
@ -83,28 +82,6 @@ RCT_EXPORT_MODULE()
return NSOrderedSame;
}
}];
// Sort decoders in reverse priority order (highest priority first)
[decoders sortUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
if (priorityA > priorityB) {
return NSOrderedAscending;
} else if (priorityA < priorityB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
_loaders = loaders;
_decoders = decoders;
}
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
{
if (!_loaders) {
[self setUp];
}
if (RCT_DEBUG) {
@ -144,10 +121,26 @@ RCT_EXPORT_MODULE()
- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
{
if (!_decoders) {
if (!_maxConcurrentLoadingTasks) {
[self setUp];
}
if (!_decoders) {
// Get decoders, sorted in reverse priority order (highest priority first)
RCTAssert(_bridge, @"Bridge not set");
_decoders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageDataDecoder)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
if (priorityA > priorityB) {
return NSOrderedAscending;
} else if (priorityA < priorityB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
}
if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
@ -295,7 +288,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
// All access to URL cache must be serialized
if (!_URLCacheQueue) {
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
[self setUp];
}
dispatch_async(_URLCacheQueue, ^{
@ -539,6 +532,9 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
completionHandler:completionHandler];
} else {
if (!_URLCacheQueue) {
[self setUp];
}
dispatch_async(_URLCacheQueue, ^{
dispatch_block_t decodeBlock = ^{

View File

@ -141,17 +141,8 @@ RCT_EXPORT_MODULE()
}
if (!_handlers) {
// get handlers
NSMutableArray<id<RCTURLRequestHandler>> *handlers = [NSMutableArray array];
for (Class moduleClass in _bridge.moduleClasses) {
if ([moduleClass conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
[handlers addObject:[_bridge moduleForClass:moduleClass]];
}
}
// Sort handlers in reverse priority order (highest priority first)
[handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
// Get handlers, sorted in reverse priority order (highest priority first)
_handlers = [[_bridge modulesConformingToProtocol:@protocol(RCTURLRequestHandler)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
if (priorityA > priorityB) {
@ -162,8 +153,6 @@ RCT_EXPORT_MODULE()
return NSOrderedSame;
}
}];
_handlers = handlers;
}
if (RCT_DEBUG) {

View File

@ -45,29 +45,25 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
@property (nonatomic, weak) RCTBridge *parentBridge;
@property (nonatomic, weak) id<RCTJavaScriptExecutor> javaScriptExecutor;
@property (nonatomic, assign) BOOL moduleSetupComplete;
@end
@implementation RCTBatchedBridge
{
BOOL _loading;
BOOL _valid;
BOOL _wasBatchActive;
NSMutableArray<dispatch_block_t> *_pendingCalls;
NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
NSArray<RCTModuleData *> *_moduleDataByID;
NSDictionary<NSString *, id<RCTBridgeModule>> *_modulesByName_DEPRECATED;
NSArray<Class> *_moduleClassesByID;
CADisplayLink *_jsDisplayLink;
NSMutableSet<RCTModuleData *> *_frameUpdateObservers;
// Bridge startup stats (TODO: capture in perf logger)
NSUInteger _syncInitializedModules;
NSUInteger _asyncInitializedModules;
}
@synthesize flowID = _flowID;
@synthesize flowIDMap = _flowIDMap;
@synthesize loading = _loading;
@synthesize valid = _valid;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
@ -122,11 +118,7 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
}];
// Synchronously initialize all native modules that cannot be loaded lazily
[self initModules];
#if RCT_DEBUG
_syncInitializedModules = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
#endif
[self initModulesWithDispatchGroup:initModulesAndLoadSource];
if (RCTProfileIsProfiling()) {
// Depends on moduleDataByID being loaded
@ -145,17 +137,10 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
// Asynchronously gather the module config
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
if (weakSelf.isValid) {
if (weakSelf.valid) {
RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig);
config = [weakSelf moduleConfig];
RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig);
#if RCT_DEBUG
NSInteger total = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
_asyncInitializedModules = total - _syncInitializedModules;
#endif
}
});
@ -217,7 +202,7 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
- (NSArray<Class> *)moduleClasses
{
if (RCT_DEBUG && self.isValid && _moduleClassesByID == nil) {
if (RCT_DEBUG && _valid && _moduleClassesByID == nil) {
RCTLogError(@"Bridge modules have not yet been initialized. You may be "
"trying to access a module too early in the startup procedure.");
}
@ -234,8 +219,7 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
- (id)moduleForName:(NSString *)moduleName
{
RCTModuleData *moduleData = _moduleDataByName[moduleName];
return moduleData.instance;
return _moduleDataByName[moduleName].instance;
}
- (NSArray *)configForModuleName:(NSString *)moduleName
@ -250,14 +234,11 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
return (id)kCFNull;
}
- (void)initModules
- (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
RCTAssertMainThread();
RCTPerformanceLoggerStart(RCTPLNativeModuleInit);
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [NSMutableDictionary new];
NSArray<id<RCTBridgeModule>> *extraModules = nil;
if (self.delegate) {
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
@ -267,95 +248,133 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
extraModules = self.moduleProvider();
}
for (id<RCTBridgeModule> module in extraModules) {
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
if (RCT_DEBUG && !RCTRunningInTestEnvironment()) {
// Check for unexported modules
static Class *classes;
static unsigned int classCount;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
classes = objc_copyClassList(&classCount);
});
NSMutableSet *moduleClasses = [NSMutableSet new];
[moduleClasses addObjectsFromArray:RCTGetModuleClasses()];
[moduleClasses addObjectsFromArray:[extraModules valueForKeyPath:@"class"]];
for (unsigned int i = 0; i < classCount; i++)
{
Class cls = classes[i];
Class superclass = cls;
while (superclass)
{
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
{
if (![moduleClasses containsObject:cls]) {
RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
"RCT_EXPORT_MODULE()?", cls);
}
break;
}
superclass = class_getSuperclass(superclass);
}
}
}
SEL setBridgeSelector = NSSelectorFromString(@"setBridge:");
IMP objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];
// Set up moduleData and pre-initialize module instances
NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
for (Class moduleClass in RCTGetModuleClasses()) {
// Set up moduleData for pre-initialized module instances
for (id<RCTBridgeModule> module in extraModules) {
Class moduleClass = [module class];
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
id module = preregisteredModules[moduleName];
if (!module) {
// Check if the module class, or any of its superclasses override init
// or setBridge:, or has exported constants. If they do, we assume that
// they are expecting to be initialized when the bridge first loads.
if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod ||
[moduleClass instancesRespondToSelector:setBridgeSelector] ||
RCTClassOverridesInstanceMethod(moduleClass, @selector(constantsToExport))) {
module = [moduleClass new];
if (!module) {
module = (id)kCFNull;
}
}
}
// Check for module name collisions.
// It's OK to have a name collision as long as the second instance is null.
if (module != (id)kCFNull && moduleDataByName[moduleName] && !preregisteredModules[moduleName]) {
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the name "
"'%@', but name was already registered by class %@", moduleClass,
moduleName, moduleDataByName[moduleName].moduleClass);
}
// Instantiate moduleData (TODO: defer this until config generation)
RCTModuleData *moduleData;
if (module) {
if (module != (id)kCFNull) {
moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
bridge:self];
}
} else {
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
bridge:self];
}
if (RCT_DEBUG) {
// Check for name collisions between preregistered modules
RCTModuleData *moduleData = moduleDataByName[moduleName];
if (moduleData) {
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
"name '%@', but name was already registered by class %@",
moduleClass, moduleName, moduleData.moduleClass);
continue;
}
}
// Instantiate moduleData container
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
bridge:self];
moduleDataByName[moduleName] = moduleData;
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}
// Set up moduleData for automatically-exported modules
for (Class moduleClass in RCTGetModuleClasses()) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
// Check for module name collisions
RCTModuleData *moduleData = moduleDataByName[moduleName];
if (moduleData) {
if (moduleData.hasInstance) {
// Existing module was preregistered, so it takes precedence
continue;
} else if ([moduleClass new] == nil) {
// The new module returned nil from init, so use the old module
continue;
} else if ([moduleData.moduleClass new] != nil) {
// Both modules were non-nil, so it's unclear which should take precedence
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
"name '%@', but name was already registered by class %@",
moduleClass, moduleName, moduleData.moduleClass);
}
}
// Instantiate moduleData (TODO: can we defer this until config generation?)
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
bridge:self];
moduleDataByName[moduleName] = moduleData;
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}
// Store modules
_moduleDataByID = [moduleDataByID copy];
_moduleDataByName = [moduleDataByName copy];
_moduleClassesByID = [moduleDataByID valueForKey:@"moduleClass"];
_moduleClassesByID = [moduleClassesByID copy];
/**
* The executor is a bridge module, wait for it to be created and set it before
* any other module has access to the bridge
*/
// The executor is a bridge module, wait for it to be created and set it up
// before any other module has access to the bridge.
_javaScriptExecutor = [self moduleForClass:self.executorClass];
// Dispatch module init onto main thead for those modules that require it
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance) {
[moduleData setBridgeForInstance];
// Modules that were pre-initialized need to be set up before bridge init
// has finished, otherwise the caller may try to access the module
// directly rather than via `[bridge moduleForClass:]`, which won't
// trigger the lazy initialization process.
(void)[moduleData instance];
}
}
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance) {
[moduleData finishSetupForInstance];
}
}
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance) {
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];
}
});
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTDidCreateNativeModules
object:self userInfo:@{@"bridge": self}];
#pragma clang diagnostic pop
// From this point on, RCTDidInitializeModuleNotification notifications will
// be sent the first time a module is accessed.
_moduleSetupComplete = YES;
RCTPerformanceLoggerEnd(RCTPLNativeModuleInit);
}
@ -418,7 +437,7 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
- (void)injectJSONConfiguration:(NSString *)configJSON
onComplete:(void (^)(NSError *))onComplete
{
if (!self.valid) {
if (!_valid) {
return;
}
@ -429,7 +448,7 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
- (void)executeSourceCode:(NSData *)sourceCode
{
if (!self.valid || !_javaScriptExecutor) {
if (!_valid || !_javaScriptExecutor) {
return;
}
@ -438,7 +457,7 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
sourceCodeModule.scriptData = sourceCode;
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
if (!self.isValid) {
if (!_valid) {
return;
}
@ -490,7 +509,7 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
{
RCTAssertMainThread();
if (!self.isValid || !self.loading) {
if (!_valid || !_loading) {
return;
}
@ -570,7 +589,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
- (void)invalidate
{
if (!self.valid) {
if (!_valid) {
return;
}
@ -615,7 +634,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
_moduleDataByName = nil;
_moduleDataByID = nil;
_moduleClassesByID = nil;
_modulesByName_DEPRECATED = nil;
_frameUpdateObservers = nil;
_pendingCalls = nil;
@ -771,7 +789,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
RCTFatal(error);
}
if (!self.isValid) {
if (!_valid) {
return;
}
[self handleBuffer:json batchEnded:YES];
@ -793,7 +811,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
RCTFatal(error);
}
if (!self.isValid) {
if (!_valid) {
return;
}
[self handleBuffer:json batchEnded:YES];
@ -931,7 +949,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
methodID:(NSUInteger)methodID
params:(NSArray *)params
{
if (!self.isValid) {
if (!_valid) {
return NO;
}
@ -989,7 +1007,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
[self updateJSDisplayLinkState];
RCTProfileImmediateEvent(0, @"JS Thread Tick", displayLink.timestamp, 'g');
RCT_PROFILE_END_EVENT(0, @"objc_call", nil);
@ -1022,24 +1039,3 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
}
@end
@implementation RCTBatchedBridge(Deprecated)
- (NSDictionary *)modules
{
if (!_modulesByName_DEPRECATED) {
// Check classes are set up
[self moduleClasses];
NSMutableDictionary *modulesByName = [NSMutableDictionary new];
for (NSString *moduleName in _moduleDataByName) {
id module = [self moduleForName:moduleName];
if (module) {
modulesByName[moduleName] = module;
}
};
_modulesByName_DEPRECATED = [modulesByName copy];
}
return _modulesByName_DEPRECATED;
}
@end

View File

@ -59,6 +59,11 @@
*/
@property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor;
/**
* Used by RCTModuleData
*/
@property (nonatomic, assign, readonly) BOOL moduleSetupComplete;
/**
* Used by RCTModuleData to register the module for frame updates after it is
* lazily initialized.

View File

@ -110,11 +110,18 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class);
* Retrieve a bridge module instance by name or class. Note that modules are
* lazily instantiated, so calling these methods for the first time with a given
* module name/class may cause the class to be sychronously instantiated,
* blocking both the calling thread and main thread for a short time.
* potentially blocking both the calling thread and main thread for a short time.
*/
- (id)moduleForName:(NSString *)moduleName;
- (id)moduleForClass:(Class)moduleClass;
/**
* Convenience method for retrieving all modules conforming to a given protocol.
* Modules will be sychronously instantiated if they haven't already been,
* potentially blocking both the calling thread and main thread for a short time.
*/
- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol;
/**
* All registered bridge module classes.
*/
@ -173,31 +180,3 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class);
- (BOOL)isBatchActive;
@end
/**
* These features are deprecated and should not be used.
*/
@interface RCTBridge (Deprecated)
/**
* This notification used to fire after all native modules has been initialized,
* but now that native modules are instantiated lazily on demand, its original
* purpose is meaningless.
*
* If you need to access a module, you can do so as soon as the bridge has been
* initialized, by calling `[bridge moduleForClass:]`. If you need to know when
* an individual module has been instantiated, add an observer for the
* `RCTDidInitializeModuleNotification` instead.
*/
RCT_EXTERN NSString *const RCTDidCreateNativeModules
__deprecated_msg("Use RCTDidInitializeModuleNotification to observe init of individual modules");
/**
* Accessing the modules property causes all modules to be eagerly initialized,
* which stalls the main thread. Use moduleClasses to enumerate through modules
* without causing them to be instantiated.
*/
@property (nonatomic, copy, readonly) NSDictionary *modules
__deprecated_msg("Use moduleClasses and/or moduleForName: instead");
@end

View File

@ -104,34 +104,6 @@ dispatch_queue_t RCTJSThread;
// Set up JS thread
RCTJSThread = (id)kCFNull;
if (RCT_DEBUG && !RCTRunningInTestEnvironment()) {
// Set up module classes
static unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++)
{
Class cls = classes[i];
Class superclass = cls;
while (superclass)
{
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
{
if (![RCTModuleClasses containsObject:cls]) {
RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
"RCT_EXPORT_MODULE()?", cls);
}
break;
}
superclass = class_getSuperclass(superclass);
}
}
free(classes);
}
});
}
@ -238,6 +210,20 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)];
}
- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol
{
NSMutableArray *modules = [NSMutableArray new];
for (Class moduleClass in self.moduleClasses) {
if ([moduleClass conformsToProtocol:protocol]) {
id module = [self moduleForClass:moduleClass];
if (module) {
[modules addObject:module];
}
}
}
return [modules copy];
}
- (RCTEventDispatcher *)eventDispatcher
{
return [self moduleForClass:[RCTEventDispatcher class]];
@ -314,14 +300,3 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
@end
@implementation RCTBridge(Deprecated)
NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules";
- (NSDictionary *)modules
{
return self.batchedBridge.modules;
}
@end

View File

@ -21,21 +21,7 @@
bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
bridge:(RCTBridge *)bridge;
/**
* Sets the bridge for the module instance. This is only needed when using the
* `initWithModuleInstance:bridge:` constructor. Otherwise, the bridge will be set
* automatically when the module is first accessed.
*/
- (void)setBridgeForInstance;
/**
* Sets the methodQueue and performs the remaining setup for the module. This is
* only needed when using the `initWithModuleInstance:bridge:` constructor.
* Otherwise it will be done automatically when the module is first accessed.
*/
- (void)finishSetupForInstance;
bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
/**
* Calls `constantsToExport` on the module and stores the result. Note that
@ -59,6 +45,11 @@
*/
@property (nonatomic, assign, readonly) BOOL hasInstance;
/**
* Returns YES if module instance must be created on the main thread.
*/
@property (nonatomic, assign, readonly) BOOL requiresMainThreadSetup;
/**
* 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
@ -73,9 +64,8 @@
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
/**
* Returns the module config. Note that this will init the module if it has
* not already been created. This method can be called on any thread, but will
* block the main thread briefly if the module implements `constantsToExport`.
* Returns the module config. Calls `gatherConstants` internally, so the same
* usage caveats apply.
*/
@property (nonatomic, copy, readonly) NSArray *config;

View File

@ -28,17 +28,37 @@
@synthesize instance = _instance;
@synthesize methodQueue = _methodQueue;
- (instancetype)initWithModuleClass:(Class)moduleClass
bridge:(RCTBridge *)bridge
- (void)setUp
{
if ((self = [super init])) {
_moduleClass = moduleClass;
_bridge = bridge;
_implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)];
_implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector(partialBatchDidFlush)];
_instanceLock = [NSLock new];
static IMP objectInitMethod;
static SEL setBridgeSelector;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];
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 ||
[_moduleClass instancesRespondToSelector:setBridgeSelector] ||
RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport)) ||
[_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod;
}
- (instancetype)initWithModuleClass:(Class)moduleClass
bridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
_moduleClass = moduleClass;
[self setUp];
}
return self;
}
@ -46,8 +66,11 @@
- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
bridge:(RCTBridge *)bridge
{
if ((self = [self initWithModuleClass:[instance class] bridge:bridge])) {
if ((self = [super init])) {
_bridge = bridge;
_instance = instance;
_moduleClass = [instance class];
[self setUp];
}
return self;
}
@ -56,9 +79,47 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
#pragma mark - private setup methods
- (void)setUpInstanceAndBridge
{
[_instanceLock lock];
if (!_setupComplete && _bridge.valid) {
if (!_instance) {
if (RCT_DEBUG && _requiresMainThreadSetup) {
RCTAssertMainThread();
}
_instance = [_moduleClass new];
if (!_instance) {
// Module init returned nil, probably because automatic instantatiation
// of the module is not supported, and it is supposed to be passed in to
// the bridge constructor. Mark setup complete to avoid doing more work.
_setupComplete = YES;
RCTLogWarn(@"The module %@ is returning nil from its constructor. You "
"may need to instantiate it yourself and pass it into the "
"bridge.", _moduleClass);
}
}
// Bridge must be set before methodQueue is set up, as methodQueue
// initialization requires it (View Managers get their queue by calling
// self.bridge.uiManager.methodQueue)
[self setBridgeForInstance];
}
[_instanceLock unlock];
// This is called outside of the lock in order to prevent deadlock issues
// because the logic in `setUpMethodQueue` can cause `moduleData.instance`
// to be accessed re-entrantly.
[self setUpMethodQueue];
// This is called outside of the lock in order to prevent deadlock issues
// because the logic in `finishSetupForInstance` can cause
// `moduleData.instance` to be accessed re-entrantly.
if (_bridge.moduleSetupComplete) {
[self finishSetupForInstance];
}
}
- (void)setBridgeForInstance
{
RCTAssert(_instance, @"setBridgeForInstance called before %@ initialized", self.name);
if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) {
@try {
[(id)_instance setValue:_bridge forKey:@"bridge"];
@ -73,9 +134,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
- (void)finishSetupForInstance
{
if (!_setupComplete) {
if (!_setupComplete && _instance) {
_setupComplete = YES;
[self setUpMethodQueue];
[_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification
object:_bridge
@ -85,8 +145,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
- (void)setUpMethodQueue
{
if (!_methodQueue) {
RCTAssert(_instance, @"setUpMethodQueue called before %@ initialized", self.name);
if (_instance && !_methodQueue) {
BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
if (implementsMethodQueue) {
_methodQueue = _instance.methodQueue;
@ -122,19 +181,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
- (id<RCTBridgeModule>)instance
{
[_instanceLock lock];
if (!_setupComplete) {
if (!_instance) {
_instance = [_moduleClass new];
if (_requiresMainThreadSetup) {
// The chances of deadlock here are low, because module init very rarely
// calls out to other threads, however we can't control when a module might
// get accessed by client code during bridge setup, and a very low risk of
// deadlock is better than a fairly high risk of an assertion being thrown.
RCTExecuteOnMainThread(^{
[self setUpInstanceAndBridge];
}, YES);
} else {
[self setUpInstanceAndBridge];
}
// Bridge must be set before methodQueue is set up, as methodQueue
// initialization requires it (View Managers get their queue by calling
// self.bridge.uiManager.methodQueue)
[self setBridgeForInstance];
}
[_instanceLock unlock];
[self finishSetupForInstance];
return _instance;
}
@ -183,8 +242,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
{
if (!_constantsToExport) {
if (RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport))) {
RCTAssert(_instance, @"constantsToExport called before instance created.");
RCTExecuteOnMainThread(^{
[self setUpInstanceAndBridge];
_constantsToExport = [_instance constantsToExport] ?: @{};
}, YES);
} else {
@ -195,7 +254,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
- (NSArray *)config
{
[self gatherConstants];
__block NSDictionary<NSString *, id> *constants = _constantsToExport;
_constantsToExport = nil; // Not needed anymore
if (constants.count == 0 && self.methods.count == 0) {
return (id)kCFNull; // Nothing to export
@ -229,7 +290,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
- (dispatch_queue_t)methodQueue
{
[self instance];
(void)[self instance];
return _methodQueue;
}

View File

@ -52,6 +52,9 @@ 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");
return _bridge.uiManager.methodQueue;
}