react-native/React/Base/RCTBatchedBridge.m

1004 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 "RCTContextExecutor.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:@"RCTContextExecutor"] || \
[[[NSThread currentThread] name] isEqualToString:@"com.facebook.React.JavaScript"], \
@"This method must be called on JS thread")
NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification";
NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
/**
* Must be kept in sync with `MessageQueue.js`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParamss,
};
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;
}
- (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:self
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) {
dispatch_async(bridgeQueue, ^{
[weakSelf 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, @"init,download", cookie, @"JavaScript download", 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;
}
- (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:. 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]) {
module = [moduleClass new];
if (!module) {
module = [NSNull null];
}
}
}
// Check for module name collisions.
// It's OK to have a name collision as long as the second instance is null.
if (module != [NSNull class] && _moduleDataByName[moduleName]) {
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the name "
"'%@', but name was already registered by class %@", moduleClass,
moduleName, _moduleDataByName[moduleName]);
}
// Instantiate moduleData (TODO: defer this until config generation)
RCTModuleData *moduleData;
if (module) {
if (module != [NSNull null]) {
moduleData = [[RCTModuleData alloc] initWithModuleInstance:module];
}
} 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) {
[moduleData setBridgeForInstance:self];
}
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance) {
[moduleData methodQueue]; // initialize the queue
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules
object:self];
RCTPerformanceLoggerEnd(RCTPLNativeModuleInit);
}
- (void)setUpExecutor
{
[_javaScriptExecutor setUp];
}
- (void)registerModuleForFrameUpdates:(RCTModuleData *)moduleData
{
if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:moduleData];
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
__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 == [RCTContextExecutor 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:[RCTContextExecutor 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 }];
});
}];
}
- (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;
[[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 ?: [RCTContextExecutor 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();
_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;
}];
});
}
- (void)logMessage:(NSString *)message level:(NSString *)level
{
if (RCT_DEBUG) {
[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
*/
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 ?: @[]];
}
}];
}
/**
* 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();
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
if (error) {
RCTFatal(error);
}
if (!self.isValid) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
[self handleBuffer:json batchEnded:YES];
};
[_javaScriptExecutor callFunctionOnModule:module
method:method
arguments:args
callback:processResponse];
}
- (void)_actuallyInvokeCallback:(NSNumber *)cbID
arguments:(NSArray *)args
{
RCTAssertJSThread();
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
if (error) {
RCTFatal(error);
}
if (!self.isValid) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
[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<NSArray *> *)buffer
{
NSArray<NSArray *> *requestsArray = [RCTConvert NSArrayArray:buffer];
if (RCT_DEBUG && requestsArray.count <= RCTBridgeFieldParamss) {
RCTLogError(@"Buffer should contain at least %tu sub-arrays. Only found %tu",
RCTBridgeFieldParamss + 1, requestsArray.count);
return;
}
NSArray<NSNumber *> *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
NSArray<NSNumber *> *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
NSArray<NSArray *> *paramsArrays = requestsArray[RCTBridgeFieldParamss];
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();
#if RCT_DEV
NSString *_threadName = RCTCurrentThreadName();
RCT_PROFILE_BEGIN_EVENT(0, _threadName, nil);
#endif
NSOrderedSet *calls = [buckets objectForKey:queue];
@autoreleasepool {
for (NSNumber *indexObj in calls) {
NSUInteger index = indexObj.unsignedIntegerValue;
[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);
}
}
}
- (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;
}
RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"[%@ %@]", moduleData.name, method.JSMethodName], nil);
@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));
}
if (RCTProfileIsProfiling()) {
NSMutableDictionary *args = [method.profileArgs mutableCopy];
args[@"method"] = method.JSMethodName;
args[@"args"] = RCTJSONStringify(RCTNullIfNil(params), NULL);
RCT_PROFILE_END_EVENT(0, @"objc_call", args);
}
return YES;
}
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertJSThread();
RCT_PROFILE_BEGIN_EVENT(0, @"DispatchFrameUpdate", 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();
RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp], nil);
[observer didUpdateFrame:frameUpdate];
RCT_PROFILE_END_EVENT(0, @"objc_call,fps", nil);
} 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