react-native/React/Base/RCTBatchedBridge.m
Nick Lockwood 855d411321 Moved constants export to bridge init
Summary:
public
Lazy export of module constants required a sync dispatch to the main thread, which was deadlocking in some of our projects.

This moves the constants export to the initial bridge init, which may slightly increase initial startup time, but avoids the deadlock.

Reviewed By: javache

Differential Revision: D2911295

fb-gh-sync-id: 0d14a629ac4fc7ee21acd293c09595c18232659b
shipit-source-id: 0d14a629ac4fc7ee21acd293c09595c18232659b
2016-02-09 03:30:32 -08:00

1038 lines
29 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTBridgeMethod.h"
#import "RCTConvert.h"
#import "RCTJSCExecutor.h"
#import "RCTFrameUpdate.h"
#import "RCTJavaScriptLoader.h"
#import "RCTLog.h"
#import "RCTModuleData.h"
#import "RCTPerformanceLogger.h"
#import "RCTProfile.h"
#import "RCTSourceCode.h"
#import "RCTUtils.h"
#define RCTAssertJSThread() \
RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTJSCExecutor"] || \
[[[NSThread currentThread] name] isEqualToString:@"com.facebook.React.JavaScript"], \
@"This method must be called on JS thread")
/**
* Must be kept in sync with `MessageQueue.js`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParams,
RCTBridgeFieldCallID,
};
RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
@interface RCTBatchedBridge : RCTBridge
@property (nonatomic, weak) RCTBridge *parentBridge;
@end
@implementation RCTBatchedBridge
{
BOOL _loading;
BOOL _valid;
BOOL _wasBatchActive;
__weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
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;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
RCTAssertMainThread();
RCTAssertParam(bridge);
if ((self = [super initWithBundleURL:bridge.bundleURL
moduleProvider:bridge.moduleProvider
launchOptions:bridge.launchOptions])) {
_parentBridge = bridge;
/**
* Set Initial State
*/
_valid = YES;
_loading = YES;
_pendingCalls = [NSMutableArray new];
_frameUpdateObservers = [NSMutableSet new];
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
[RCTBridge setCurrentBridge:self];
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptWillStartLoadingNotification
object:_parentBridge userInfo:@{@"bridge": self}];
[self start];
}
return self;
}
- (void)start
{
dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
// Asynchronously load source code
dispatch_group_enter(initModulesAndLoadSource);
__weak RCTBatchedBridge *weakSelf = self;
__block NSData *sourceCode;
[self loadSource:^(NSError *error, NSData *source) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
sourceCode = source;
dispatch_group_leave(initModulesAndLoadSource);
}];
// Synchronously initialize all native modules that cannot be loaded lazily
[self initModules];
#if RCT_DEBUG
_syncInitializedModules = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
#endif
if (RCTProfileIsProfiling()) {
// Depends on moduleDataByID being loaded
RCTProfileHookModules(self);
}
__block NSString *config;
dispatch_group_enter(initModulesAndLoadSource);
dispatch_async(bridgeQueue, ^{
dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();
// Asynchronously initialize the JS executor
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
[weakSelf setUpExecutor];
});
// Asynchronously gather the module config
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
if (weakSelf.isValid) {
RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig);
config = [weakSelf moduleConfig];
RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig);
#if RCT_DEBUG
NSInteger total = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
_asyncInitializedModules = total - _syncInitializedModules;
#endif
}
});
dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
// We're not waiting for this to complete to leave dispatch group, since
// injectJSONConfiguration and executeSourceCode will schedule operations
// on the same queue anyway.
RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig);
[weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig);
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
}];
dispatch_group_leave(initModulesAndLoadSource);
});
});
dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{
RCTBatchedBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode];
}
});
}
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad
{
RCTPerformanceLoggerStart(RCTPLScriptDownload);
NSUInteger cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil);
// Suppress a warning if RCTProfileBeginAsyncEvent gets compiled out
(void)cookie;
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSData *source) {
RCTProfileEndAsyncEvent(0, @"native", cookie, @"JavaScript download", @"JS async", nil);
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
_onSourceLoad(error, source);
};
if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) {
[self.delegate loadSourceForBridge:_parentBridge withBlock:onSourceLoad];
} else if (self.bundleURL) {
[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onComplete:onSourceLoad];
} else {
// Allow testing without a script
dispatch_async(dispatch_get_main_queue(), ^{
[self didFinishLoading];
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge userInfo:@{@"bridge": self}];
});
onSourceLoad(nil, nil);
}
}
- (NSArray<Class> *)moduleClasses
{
if (RCT_DEBUG && self.isValid && _moduleClassesByID == nil) {
RCTLogError(@"Bridge modules have not yet been initialized. You may be "
"trying to access a module too early in the startup procedure.");
}
return _moduleClassesByID;
}
/**
* Used by RCTUIManager
*/
- (RCTModuleData *)moduleDataForName:(NSString *)moduleName
{
return _moduleDataByName[moduleName];
}
- (id)moduleForName:(NSString *)moduleName
{
RCTModuleData *moduleData = _moduleDataByName[moduleName];
return moduleData.instance;
}
- (NSArray *)configForModuleName:(NSString *)moduleName
{
RCTModuleData *moduleData = _moduleDataByName[moduleName];
if (!moduleData) {
moduleData = _moduleDataByName[[@"RCT" stringByAppendingString:moduleName]];
}
if (moduleData) {
return moduleData.config;
}
return (id)kCFNull;
}
- (void)initModules
{
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:)]) {
extraModules = [self.delegate extraModulesForBridge:_parentBridge];
}
} else if (self.moduleProvider) {
extraModules = self.moduleProvider();
}
for (id<RCTBridgeModule> module in extraModules) {
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
}
SEL setBridgeSelector = NSSelectorFromString(@"setBridge:");
IMP objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];
// Set up moduleData and pre-initialize module instances
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
for (Class moduleClass in RCTGetModuleClasses()) {
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 (moduleData) {
moduleDataByName[moduleName] = moduleData;
[moduleDataByID addObject:moduleData];
}
}
// Store modules
_moduleDataByID = [moduleDataByID copy];
_moduleDataByName = [moduleDataByName copy];
_moduleClassesByID = [moduleDataByID valueForKey:@"moduleClass"];
/**
* The executor is a bridge module, wait for it to be created and set it before
* any other module has access to the bridge
*/
_javaScriptExecutor = [self moduleForClass:self.executorClass];
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance) {
[moduleData setBridgeForInstance];
}
}
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance) {
[moduleData finishSetupForInstance];
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTDidCreateNativeModules
object:self userInfo:@{@"bridge": self}];
#pragma clang diagnostic pop
RCTPerformanceLoggerEnd(RCTPLNativeModuleInit);
}
- (void)setUpExecutor
{
[_javaScriptExecutor setUp];
}
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
withModuleData:(RCTModuleData *)moduleData
{
if (![_frameUpdateObservers containsObject:moduleData]) {
if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:moduleData];
// Don't access the module instance via moduleData, as this will cause deadlock
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)module;
__weak typeof(self) weakSelf = self;
__weak typeof(_javaScriptExecutor) weakJavaScriptExecutor = _javaScriptExecutor;
observer.pauseCallback = ^{
[weakJavaScriptExecutor executeBlockOnJavaScriptQueue:^{
[weakSelf updateJSDisplayLinkState];
}];
};
}
}
}
- (NSString *)moduleConfig
{
NSMutableArray<NSArray *> *config = [NSMutableArray new];
for (RCTModuleData *moduleData in _moduleDataByID) {
if (self.executorClass == [RCTJSCExecutor class]) {
[config addObject:@[moduleData.name]];
} else {
[config addObject:RCTNullIfNil(moduleData.config)];
}
}
return RCTJSONStringify(@{
@"remoteModuleConfig": config,
}, NULL);
}
- (void)updateJSDisplayLinkState
{
RCTAssertJSThread();
BOOL pauseDisplayLink = YES;
for (RCTModuleData *moduleData in _frameUpdateObservers) {
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
if (!observer.paused) {
pauseDisplayLink = NO;
break;
}
}
_jsDisplayLink.paused = pauseDisplayLink;
}
- (void)injectJSONConfiguration:(NSString *)configJSON
onComplete:(void (^)(NSError *))onComplete
{
if (!self.valid) {
return;
}
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
callback:onComplete];
}
- (void)executeSourceCode:(NSData *)sourceCode
{
if (!self.valid || !_javaScriptExecutor) {
return;
}
RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]];
sourceCodeModule.scriptURL = self.bundleURL;
sourceCodeModule.scriptData = sourceCode;
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
if (!self.isValid) {
return;
}
if (loadError) {
dispatch_async(dispatch_get_main_queue(), ^{
[self stopLoadingWithError:loadError];
});
return;
}
// Register the display link to start sending js calls after everything is setup
NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
[_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
// Perform the state update and notification on the main thread, so we can't run into
// timing issues with RCTRootView
dispatch_async(dispatch_get_main_queue(), ^{
[self didFinishLoading];
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge userInfo:@{@"bridge": self}];
});
}];
#if RCT_DEV
if (RCTGetURLQueryParam(self.bundleURL, @"hot")) {
NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash
[self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path]];
}
#endif
}
- (void)didFinishLoading
{
_loading = NO;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
for (dispatch_block_t call in _pendingCalls) {
call();
}
}];
}
- (void)stopLoadingWithError:(NSError *)error
{
RCTAssertMainThread();
if (!self.isValid || !self.loading) {
return;
}
_loading = NO;
[_javaScriptExecutor invalidate];
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:_parentBridge userInfo:@{@"bridge": self, @"error": error}];
RCTFatal(error);
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleURL
moduleProvider:(__unused RCTBridgeModuleProviderBlock)block
launchOptions:(__unused NSDictionary *)launchOptions)
/**
* Prevent super from calling setUp (that'd create another batchedBridge)
*/
- (void)setUp {}
- (void)bindKeys {}
- (void)reload
{
[_parentBridge reload];
}
- (Class)executorClass
{
return _parentBridge.executorClass ?: [RCTJSCExecutor class];
}
- (void)setExecutorClass:(Class)executorClass
{
RCTAssertMainThread();
_parentBridge.executorClass = executorClass;
}
- (NSURL *)bundleURL
{
return _parentBridge.bundleURL;
}
- (void)setBundleURL:(NSURL *)bundleURL
{
_parentBridge.bundleURL = bundleURL;
}
- (id<RCTBridgeDelegate>)delegate
{
return _parentBridge.delegate;
}
- (BOOL)isLoading
{
return _loading;
}
- (BOOL)isValid
{
return _valid;
}
- (void)dispatchBlock:(dispatch_block_t)block
queue:(dispatch_queue_t)queue
{
if (queue == RCTJSThread) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else if (queue) {
dispatch_async(queue, block);
}
}
#pragma mark - RCTInvalidating
- (void)invalidate
{
if (!self.valid) {
return;
}
RCTAssertMainThread();
RCTAssert(_javaScriptExecutor != nil, @"Can't complete invalidation without a JS executor");
_loading = NO;
_valid = NO;
if ([RCTBridge currentBridge] == self) {
[RCTBridge setCurrentBridge:nil];
}
// Invalidate modules
dispatch_group_t group = dispatch_group_create();
for (RCTModuleData *moduleData in _moduleDataByName.allValues) {
if (moduleData.instance == _javaScriptExecutor) {
continue;
}
if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
dispatch_group_enter(group);
[self dispatchBlock:^{
[(id<RCTInvalidating>)moduleData.instance invalidate];
dispatch_group_leave(group);
} queue:moduleData.methodQueue];
}
[moduleData invalidate];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
[_jsDisplayLink invalidate];
_jsDisplayLink = nil;
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
if (RCTProfileIsProfiling()) {
RCTProfileUnhookModules(self);
}
_moduleDataByName = nil;
_moduleDataByID = nil;
_moduleClassesByID = nil;
_modulesByName_DEPRECATED = nil;
_frameUpdateObservers = nil;
_pendingCalls = nil;
if (_flowIDMap != NULL) {
CFRelease(_flowIDMap);
}
}];
});
}
- (void)logMessage:(NSString *)message level:(NSString *)level
{
if (RCT_DEBUG && [_javaScriptExecutor isValid]) {
[self enqueueJSCall:@"RCTLog.logIfNoNativeHook"
args:@[level, message]];
}
}
#pragma mark - RCTBridge methods
/**
* Public. Can be invoked from any thread.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
/**
* AnyThread
*/
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge enqueueJSCall:]", nil);
NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."];
NSString *module = ids[0];
NSString *method = ids[1];
RCTProfileBeginFlowEvent();
__weak RCTBatchedBridge *weakSelf = self;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileEndFlowEvent();
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.valid) {
return;
}
if (strongSelf.loading) {
dispatch_block_t pendingCall = ^{
[weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]];
};
[strongSelf->_pendingCalls addObject:pendingCall];
} else {
[strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]];
}
}];
RCT_PROFILE_END_EVENT(0, @"", nil);
}
/**
* Called by RCTModuleMethod from any thread.
*/
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
/**
* AnyThread
*/
RCTProfileBeginFlowEvent();
__weak RCTBatchedBridge *weakSelf = self;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileEndFlowEvent();
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.valid) {
return;
}
if (strongSelf.loading) {
dispatch_block_t pendingCall = ^{
[weakSelf _actuallyInvokeCallback:cbID arguments:args ?: @[]];
};
[strongSelf->_pendingCalls addObject:pendingCall];
} else {
[strongSelf _actuallyInvokeCallback:cbID arguments:args];
}
}];
}
/**
* Private hack to support `setTimeout(fn, 0)`
*/
- (void)_immediatelyCallTimer:(NSNumber *)timer
{
RCTAssertJSThread();
dispatch_block_t block = ^{
[self _actuallyInvokeAndProcessModule:@"JSTimersExecution"
method:@"callTimers"
arguments:@[@[timer]]];
};
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
} else {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
}
}
- (void)enqueueApplicationScript:(NSData *)script
url:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCTProfileBeginFlowEvent();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCTProfileEndFlowEvent();
RCTAssertJSThread();
if (scriptLoadError) {
onComplete(scriptLoadError);
return;
}
RCT_PROFILE_BEGIN_EVENT(0, @"FetchApplicationScriptCallbacks", nil);
[_javaScriptExecutor flushedQueue:^(id json, NSError *error)
{
RCT_PROFILE_END_EVENT(0, @"js_call,init", @{
@"json": RCTNullIfNil(json),
@"error": RCTNullIfNil(error),
});
[self handleBuffer:json batchEnded:YES];
onComplete(error);
}];
}];
}
#pragma mark - Payload Generation
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
{
RCTAssertJSThread();
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
if (error) {
RCTFatal(error);
}
if (!self.isValid) {
return;
}
[self handleBuffer:json batchEnded:YES];
};
[_javaScriptExecutor callFunctionOnModule:module
method:method
arguments:args
callback:processResponse];
}
- (void)_actuallyInvokeCallback:(NSNumber *)cbID
arguments:(NSArray *)args
{
RCTAssertJSThread();
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
if (error) {
RCTFatal(error);
}
if (!self.isValid) {
return;
}
[self handleBuffer:json batchEnded:YES];
};
[_javaScriptExecutor invokeCallbackID:cbID
arguments:args
callback:processResponse];
}
#pragma mark - Payload Processing
- (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded
{
RCTAssertJSThread();
if (buffer != nil && buffer != (id)kCFNull) {
_wasBatchActive = YES;
[self handleBuffer:buffer];
[self partialBatchDidFlush];
}
if (batchEnded) {
if (_wasBatchActive) {
[self batchDidComplete];
}
_wasBatchActive = NO;
}
}
- (void)handleBuffer:(NSArray *)buffer
{
NSArray *requestsArray = [RCTConvert NSArray:buffer];
if (RCT_DEBUG && requestsArray.count <= RCTBridgeFieldParams) {
RCTLogError(@"Buffer should contain at least %tu sub-arrays. Only found %tu",
RCTBridgeFieldParams + 1, requestsArray.count);
return;
}
NSArray<NSNumber *> *moduleIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldRequestModuleIDs]];
NSArray<NSNumber *> *methodIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldMethodIDs]];
NSArray<NSArray *> *paramsArrays = [RCTConvert NSArrayArray:requestsArray[RCTBridgeFieldParams]];
int64_t callID = -1;
if (requestsArray.count > 3) {
callID = [requestsArray[RCTBridgeFieldCallID] longLongValue];
}
if (RCT_DEBUG && (moduleIDs.count != methodIDs.count || moduleIDs.count != paramsArrays.count)) {
RCTLogError(@"Invalid data message - all must be length: %zd", moduleIDs.count);
return;
}
NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsStrongMemory
capacity:_moduleDataByName.count];
[moduleIDs enumerateObjectsUsingBlock:^(NSNumber *moduleID, NSUInteger i, __unused BOOL *stop) {
RCTModuleData *moduleData = _moduleDataByID[moduleID.integerValue];
dispatch_queue_t queue = moduleData.methodQueue;
NSMutableOrderedSet<NSNumber *> *set = [buckets objectForKey:queue];
if (!set) {
set = [NSMutableOrderedSet new];
[buckets setObject:set forKey:queue];
}
[set addObject:@(i)];
}];
for (dispatch_queue_t queue in buckets) {
RCTProfileBeginFlowEvent();
dispatch_block_t block = ^{
RCTProfileEndFlowEvent();
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge handleBuffer:]", nil);
NSOrderedSet *calls = [buckets objectForKey:queue];
@autoreleasepool {
for (NSNumber *indexObj in calls) {
NSUInteger index = indexObj.unsignedIntegerValue;
if (callID != -1 && _flowIDMap != NULL) {
int64_t newFlowID = (int64_t)CFDictionaryGetValue(_flowIDMap, (const void *)(_flowID + index));
_RCTProfileEndFlowEvent(@(newFlowID));
CFDictionaryRemoveValue(_flowIDMap, (const void *)(_flowID + index));
}
[self _handleRequestNumber:index
moduleID:[moduleIDs[index] integerValue]
methodID:[methodIDs[index] integerValue]
params:paramsArrays[index]];
}
}
RCT_PROFILE_END_EVENT(0, @"objc_call,dispatch_async", @{
@"calls": @(calls.count),
});
};
if (queue == RCTJSThread) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else if (queue) {
dispatch_async(queue, block);
}
}
_flowID = callID;
}
- (void)partialBatchDidFlush
{
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.implementsPartialBatchDidFlush) {
[self dispatchBlock:^{
[moduleData.instance partialBatchDidFlush];
} queue:moduleData.methodQueue];
}
}
}
- (void)batchDidComplete
{
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.implementsBatchDidComplete) {
[self dispatchBlock:^{
[moduleData.instance batchDidComplete];
} queue:moduleData.methodQueue];
}
}
}
- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSUInteger)moduleID
methodID:(NSUInteger)methodID
params:(NSArray *)params
{
if (!self.isValid) {
return NO;
}
if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
return NO;
}
RCTModuleData *moduleData = _moduleDataByID[moduleID];
if (RCT_DEBUG && !moduleData) {
RCTLogError(@"No module found for id '%zd'", moduleID);
return NO;
}
id<RCTBridgeMethod> method = moduleData.methods[methodID];
if (RCT_DEBUG && !method) {
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, moduleData.name);
return NO;
}
@try {
[method invokeWithBridge:self module:moduleData.instance arguments:params];
}
@catch (NSException *exception) {
// Pass on JS exceptions
if ([exception.name hasPrefix:RCTFatalExceptionName]) {
@throw exception;
}
NSString *message = [NSString stringWithFormat:
@"Exception '%@' was thrown while invoking %@ on target %@ with params %@",
exception, method.JSMethodName, moduleData.name, params];
RCTFatal(RCTErrorWithMessage(message));
}
return YES;
}
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertJSThread();
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge _jsThreadUpdate:]", nil);
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (RCTModuleData *moduleData in _frameUpdateObservers) {
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
if (!observer.paused) {
RCTProfileBeginFlowEvent();
[self dispatchBlock:^{
RCTProfileEndFlowEvent();
[observer didUpdateFrame:frameUpdate];
} queue:moduleData.methodQueue];
}
}
[self updateJSDisplayLinkState];
RCTProfileImmediateEvent(0, @"JS Thread Tick", displayLink.timestamp, 'g');
RCT_PROFILE_END_EVENT(0, @"objc_call", nil);
}
- (void)startProfiling
{
RCTAssertMainThread();
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileInit(self);
}];
}
- (void)stopProfiling:(void (^)(NSData *))callback
{
RCTAssertMainThread();
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileEnd(self, ^(NSString *log) {
NSData *logData = [log dataUsingEncoding:NSUTF8StringEncoding];
callback(logData);
});
}];
}
- (BOOL)isBatchActive
{
return _wasBatchActive;
}
@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