mirror of
https://github.com/status-im/react-native.git
synced 2025-01-09 17:15:54 +00:00
331e4a96e9
Summary: The module's methodQueue wasn't being created when accessing the modules directly on the bridge, without going through JS. Preload the queue for now to fix internal breakages, but I'll figure out a better way to keep it lazy afterwards.
866 lines
26 KiB
Objective-C
866 lines
26 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 "RCTConvert.h"
|
|
#import "RCTContextExecutor.h"
|
|
#import "RCTFrameUpdate.h"
|
|
#import "RCTJavaScriptLoader.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTModuleData.h"
|
|
#import "RCTModuleMap.h"
|
|
#import "RCTModuleMethod.h"
|
|
#import "RCTPerformanceLogger.h"
|
|
#import "RCTPerfStats.h"
|
|
#import "RCTProfile.h"
|
|
#import "RCTRedBox.h"
|
|
#import "RCTSourceCode.h"
|
|
#import "RCTSparseArray.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 *RCTGetModuleClasses(void);
|
|
|
|
static id<RCTJavaScriptExecutor> RCTLatestExecutor = nil;
|
|
id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void);
|
|
id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void)
|
|
{
|
|
return RCTLatestExecutor;
|
|
}
|
|
|
|
@interface RCTBatchedBridge : RCTBridge
|
|
|
|
@property (nonatomic, weak) RCTBridge *parentBridge;
|
|
|
|
@end
|
|
|
|
@implementation RCTBatchedBridge
|
|
{
|
|
BOOL _loading;
|
|
__weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
|
|
NSMutableArray *_moduleDataByID;
|
|
RCTModuleMap *_modulesByName;
|
|
CADisplayLink *_mainDisplayLink;
|
|
CADisplayLink *_jsDisplayLink;
|
|
NSMutableSet *_frameUpdateObservers;
|
|
NSMutableArray *_scheduledCalls;
|
|
RCTSparseArray *_scheduledCallbacks;
|
|
NSURL *_sourceURL;
|
|
}
|
|
|
|
@synthesize valid = _valid;
|
|
|
|
- (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;
|
|
_moduleDataByID = [[NSMutableArray alloc] init];
|
|
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
|
_scheduledCalls = [[NSMutableArray alloc] init];
|
|
_scheduledCallbacks = [[RCTSparseArray alloc] init];
|
|
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
|
|
|
|
if (RCT_DEV) {
|
|
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
|
|
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
}
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification
|
|
object:self
|
|
userInfo:@{ @"bridge": self }];
|
|
|
|
[self start];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)start
|
|
{
|
|
_sourceURL = self.delegate ? [self.delegate sourceURLForBridge:_parentBridge] : self.bundleURL;
|
|
|
|
__weak RCTBatchedBridge *weakSelf = self;
|
|
|
|
__block NSString *sourceCode;
|
|
__block NSString *config;
|
|
|
|
dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);
|
|
dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
|
|
|
|
dispatch_group_enter(initModulesAndLoadSource);
|
|
[weakSelf loadSource:^(NSError *error, NSString *source) {
|
|
if (error) {
|
|
RCTLogError(@"%@", error);
|
|
} else {
|
|
sourceCode = source;
|
|
}
|
|
|
|
dispatch_group_leave(initModulesAndLoadSource);
|
|
}];
|
|
|
|
[self initModules];
|
|
|
|
dispatch_group_enter(initModulesAndLoadSource);
|
|
dispatch_async(bridgeQueue, ^{
|
|
dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();
|
|
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
|
|
[weakSelf setupExecutor];
|
|
});
|
|
|
|
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
|
|
if (weakSelf.isValid) {
|
|
config = [weakSelf moduleConfig];
|
|
|
|
if (RCTProfileIsProfiling()) {
|
|
RCTProfileHookModules(weakSelf);
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
|
|
[weakSelf injectJSONConfiguration:config onComplete:^(__unused NSError *error) {}];
|
|
|
|
dispatch_group_leave(initModulesAndLoadSource);
|
|
});
|
|
});
|
|
|
|
dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
|
|
if (sourceCode) {
|
|
[weakSelf executeSourceCode:sourceCode];
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad
|
|
{
|
|
RCTPerformanceLoggerStart(RCTPLScriptDownload);
|
|
RCTProfileBeginEvent();
|
|
|
|
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSString *source) {
|
|
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
|
|
RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]);
|
|
|
|
if (error) {
|
|
NSArray *stack = [error userInfo][@"stack"];
|
|
if (stack) {
|
|
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
|
|
withStack:stack];
|
|
} else {
|
|
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
|
|
withDetails:[error localizedFailureReason]];
|
|
}
|
|
|
|
NSDictionary *userInfo = @{@"bridge": self, @"error": error};
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
|
|
object:_parentBridge
|
|
userInfo:userInfo];
|
|
}
|
|
|
|
_onSourceLoad(error, source);
|
|
};
|
|
|
|
if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) {
|
|
[self.delegate loadSourceForBridge:_parentBridge withBlock:onSourceLoad];
|
|
} else if (_sourceURL) {
|
|
[RCTJavaScriptLoader loadBundleAtURL:_sourceURL
|
|
onComplete:onSourceLoad];
|
|
} else {
|
|
// Allow testing without a script
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
_loading = NO;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
|
|
object:_parentBridge
|
|
userInfo:@{ @"bridge": self }];
|
|
});
|
|
onSourceLoad(nil, nil);
|
|
}
|
|
}
|
|
|
|
- (void)initModules
|
|
{
|
|
RCTAssertMainThread();
|
|
|
|
// Register passed-in module instances
|
|
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
|
|
|
NSArray *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;
|
|
}
|
|
|
|
// Instantiate modules
|
|
_moduleDataByID = [[NSMutableArray alloc] init];
|
|
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
|
|
for (Class moduleClass in RCTGetModuleClasses()) {
|
|
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
|
|
|
|
// Check if module instance has already been registered for this name
|
|
id<RCTBridgeModule> module = modulesByName[moduleName];
|
|
|
|
if (module) {
|
|
// Preregistered instances takes precedence, no questions asked
|
|
if (!preregisteredModules[moduleName]) {
|
|
// It's OK to have a name collision as long as the second instance is nil
|
|
RCTAssert([[moduleClass alloc] init] == nil,
|
|
@"Attempted to register RCTBridgeModule class %@ for the name "
|
|
"'%@', but name was already registered by class %@", moduleClass,
|
|
moduleName, [modulesByName[moduleName] class]);
|
|
}
|
|
if ([module class] != moduleClass) {
|
|
RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered "
|
|
"in the project, but name was already registered by class %@."
|
|
"That's fine if it's intentional - just letting you know.",
|
|
moduleClass, moduleName, [modulesByName[moduleName] class]);
|
|
}
|
|
} else {
|
|
// Module name hasn't been used before, so go ahead and instantiate
|
|
module = [[moduleClass alloc] init];
|
|
}
|
|
if (module) {
|
|
modulesByName[moduleName] = module;
|
|
}
|
|
}
|
|
|
|
// Store modules
|
|
_modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName];
|
|
|
|
/**
|
|
* 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 = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)];
|
|
RCTLatestExecutor = _javaScriptExecutor;
|
|
|
|
for (id<RCTBridgeModule> module in _modulesByName.allValues) {
|
|
if ([module respondsToSelector:@selector(setBridge:)]) {
|
|
module.bridge = self;
|
|
}
|
|
|
|
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor
|
|
uid:@(_moduleDataByID.count)
|
|
instance:module];
|
|
[_moduleDataByID addObject:moduleData];
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules
|
|
object:self];
|
|
}
|
|
|
|
|
|
- (void)setupExecutor
|
|
{
|
|
[_javaScriptExecutor setUp];
|
|
|
|
}
|
|
|
|
- (NSString *)moduleConfig
|
|
{
|
|
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
|
|
for (RCTModuleData *moduleData in _moduleDataByID) {
|
|
config[moduleData.name] = moduleData.config;
|
|
|
|
|
|
// HACK(tadeu): Ensure the queue has been loaded, make it lazy later inside RCTModuleMap
|
|
(void)[moduleData queue];
|
|
|
|
if ([moduleData.instance conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
|
|
[_frameUpdateObservers addObject:moduleData];
|
|
}
|
|
}
|
|
|
|
return RCTJSONStringify(@{
|
|
@"remoteModuleConfig": config,
|
|
}, NULL);
|
|
}
|
|
|
|
- (void)injectJSONConfiguration:(NSString *)configJSON
|
|
onComplete:(void (^)(NSError *))onComplete
|
|
{
|
|
if (!self.isValid) {
|
|
return;
|
|
}
|
|
|
|
[_javaScriptExecutor injectJSONText:configJSON
|
|
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
|
|
callback:^(NSError *error) {
|
|
if (error) {
|
|
[[RCTRedBox sharedInstance] showError:error];
|
|
}
|
|
onComplete(error);
|
|
}];
|
|
}
|
|
|
|
- (void)executeSourceCode:(NSString *)sourceCode
|
|
{
|
|
_loading = NO;
|
|
|
|
if (!self.isValid || !_javaScriptExecutor) {
|
|
return;
|
|
}
|
|
|
|
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
|
sourceCodeModule.scriptURL = _sourceURL;
|
|
sourceCodeModule.scriptText = sourceCode;
|
|
|
|
static BOOL shouldDismiss = NO;
|
|
if (shouldDismiss) {
|
|
[[RCTRedBox sharedInstance] dismiss];
|
|
}
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
shouldDismiss = YES;
|
|
});
|
|
|
|
[self enqueueApplicationScript:sourceCode url:_sourceURL onComplete:^(NSError *loadError) {
|
|
|
|
if (loadError) {
|
|
[[RCTRedBox sharedInstance] showError: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];
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
|
|
object:_parentBridge
|
|
userInfo:@{ @"bridge": self }];
|
|
}];
|
|
}
|
|
|
|
|
|
RCT_NOT_IMPLEMENTED(-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;
|
|
}
|
|
|
|
- (NSDictionary *)modules
|
|
{
|
|
RCTAssert(!self.isValid || _modulesByName != nil, @"Bridge modules have not yet been initialized. "
|
|
"You may be trying to access a module too early in the startup procedure.");
|
|
|
|
return _modulesByName;
|
|
}
|
|
|
|
#pragma mark - RCTInvalidating
|
|
|
|
- (void)invalidate
|
|
{
|
|
if (!self.isValid) {
|
|
return;
|
|
}
|
|
|
|
RCTAssertMainThread();
|
|
|
|
_valid = NO;
|
|
if (RCTLatestExecutor == _javaScriptExecutor) {
|
|
RCTLatestExecutor = nil;
|
|
}
|
|
|
|
[_mainDisplayLink invalidate];
|
|
_mainDisplayLink = nil;
|
|
|
|
// Invalidate modules
|
|
dispatch_group_t group = dispatch_group_create();
|
|
for (RCTModuleData *moduleData in _moduleDataByID) {
|
|
if (moduleData.instance == _javaScriptExecutor) {
|
|
continue;
|
|
}
|
|
|
|
if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
|
|
[moduleData dispatchBlock:^{
|
|
[(id<RCTInvalidating>)moduleData.instance invalidate];
|
|
} dispatchGroup:group];
|
|
}
|
|
moduleData.queue = nil;
|
|
}
|
|
|
|
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
|
[_jsDisplayLink invalidate];
|
|
_jsDisplayLink = nil;
|
|
|
|
[_javaScriptExecutor invalidate];
|
|
_javaScriptExecutor = nil;
|
|
|
|
if (RCTProfileIsProfiling()) {
|
|
RCTProfileUnhookModules(self);
|
|
}
|
|
_moduleDataByID = nil;
|
|
_modulesByName = nil;
|
|
_frameUpdateObservers = nil;
|
|
|
|
}];
|
|
});
|
|
}
|
|
|
|
#pragma mark - RCTBridge methods
|
|
|
|
/**
|
|
* Public. Can be invoked from any thread.
|
|
*/
|
|
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
|
|
{
|
|
NSArray *ids = [moduleDotMethod componentsSeparatedByString:@"."];
|
|
|
|
[self _invokeAndProcessModule:@"BatchedBridge"
|
|
method:@"callFunctionReturnFlushedQueue"
|
|
arguments:@[ids[0], ids[1], args ?: @[]]];
|
|
}
|
|
|
|
/**
|
|
* Private hack to support `setTimeout(fn, 0)`
|
|
*/
|
|
- (void)_immediatelyCallTimer:(NSNumber *)timer
|
|
{
|
|
RCTAssertJSThread();
|
|
|
|
dispatch_block_t block = ^{
|
|
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
|
method:@"callFunctionReturnFlushedQueue"
|
|
arguments:@[@"JSTimersExecution", @"callTimers", @[@[timer]]]];
|
|
};
|
|
|
|
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
|
|
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
|
|
} else {
|
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
|
|
}
|
|
}
|
|
|
|
- (void)enqueueApplicationScript:(NSString *)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;
|
|
}
|
|
|
|
RCTProfileBeginEvent();
|
|
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
|
method:@"flushedQueue"
|
|
arguments:@[]
|
|
callback:^(id json, NSError *error) {
|
|
RCTProfileEndEvent(@"FetchApplicationScriptCallbacks", @"js_call,init", @{
|
|
@"json": RCTNullIfNil(json),
|
|
@"error": RCTNullIfNil(error),
|
|
});
|
|
|
|
[self _handleBuffer:json];
|
|
|
|
onComplete(error);
|
|
}];
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Payload Generation
|
|
|
|
/**
|
|
* Called by enqueueJSCall from any thread, or from _immediatelyCallTimer,
|
|
* on the JS thread, but only in non-batched mode.
|
|
*/
|
|
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
|
{
|
|
/**
|
|
* AnyThread
|
|
*/
|
|
|
|
RCTProfileBeginFlowEvent();
|
|
|
|
__weak RCTBatchedBridge *weakSelf = self;
|
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
|
RCTProfileEndFlowEvent();
|
|
RCTProfileBeginEvent();
|
|
|
|
RCTBatchedBridge *strongSelf = weakSelf;
|
|
if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) {
|
|
return;
|
|
}
|
|
|
|
|
|
RCT_IF_DEV(NSNumber *callID = _RCTProfileBeginFlowEvent();)
|
|
id call = @{
|
|
@"js_args": @{
|
|
@"module": module,
|
|
@"method": method,
|
|
@"args": args,
|
|
},
|
|
RCT_IF_DEV(@"call_id": callID,)
|
|
};
|
|
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
|
strongSelf->_scheduledCallbacks[args[0]] = call;
|
|
} else {
|
|
[strongSelf->_scheduledCalls addObject:call];
|
|
}
|
|
|
|
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
|
}];
|
|
}
|
|
|
|
- (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) {
|
|
[[RCTRedBox sharedInstance] showError:error];
|
|
}
|
|
|
|
if (!self.isValid) {
|
|
return;
|
|
}
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
|
|
[self _handleBuffer:json];
|
|
};
|
|
|
|
[_javaScriptExecutor executeJSCall:module
|
|
method:method
|
|
arguments:args
|
|
callback:processResponse];
|
|
}
|
|
|
|
#pragma mark - Payload Processing
|
|
|
|
- (void)_handleBuffer:(id)buffer
|
|
{
|
|
RCTAssertJSThread();
|
|
|
|
if (buffer == nil || buffer == (id)kCFNull) {
|
|
return;
|
|
}
|
|
|
|
NSArray *requestsArray = [RCTConvert NSArray:buffer];
|
|
|
|
#if RCT_DEBUG
|
|
|
|
if (![buffer isKindOfClass:[NSArray class]]) {
|
|
RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
|
|
return;
|
|
}
|
|
|
|
for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) {
|
|
id field = [requestsArray objectAtIndex:fieldIndex];
|
|
if (![field isKindOfClass:[NSArray class]]) {
|
|
RCTLogError(@"Field at index %zd in buffer must be an instance of NSArray, got %@", fieldIndex, NSStringFromClass([field class]));
|
|
return;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
|
|
NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
|
|
NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
|
|
|
|
NSUInteger numRequests = [moduleIDs count];
|
|
|
|
if (RCT_DEBUG && (numRequests != methodIDs.count || numRequests != paramsArrays.count)) {
|
|
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
|
|
return;
|
|
}
|
|
|
|
NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
|
|
valueOptions:NSPointerFunctionsStrongMemory
|
|
capacity:_moduleDataByID.count];
|
|
for (NSUInteger i = 0; i < numRequests; i++) {
|
|
RCTModuleData *moduleData = _moduleDataByID[[moduleIDs[i] integerValue]];
|
|
if (RCT_DEBUG) {
|
|
// verify that class has been registered
|
|
(void)_modulesByName[moduleData.name];
|
|
}
|
|
NSMutableOrderedSet *set = [buckets objectForKey:moduleData];
|
|
if (!set) {
|
|
set = [[NSMutableOrderedSet alloc] init];
|
|
[buckets setObject:set forKey:moduleData];
|
|
}
|
|
[set addObject:@(i)];
|
|
}
|
|
|
|
for (RCTModuleData *moduleData in buckets) {
|
|
RCTProfileBeginFlowEvent();
|
|
|
|
[moduleData dispatchBlock:^{
|
|
RCTProfileEndFlowEvent();
|
|
RCTProfileBeginEvent();
|
|
|
|
NSOrderedSet *calls = [buckets objectForKey:moduleData];
|
|
@autoreleasepool {
|
|
for (NSNumber *indexObj in calls) {
|
|
NSUInteger index = indexObj.unsignedIntegerValue;
|
|
[self _handleRequestNumber:index
|
|
moduleID:[moduleIDs[index] integerValue]
|
|
methodID:[methodIDs[index] integerValue]
|
|
params:paramsArrays[index]];
|
|
}
|
|
}
|
|
|
|
RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) });
|
|
}];
|
|
}
|
|
|
|
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
|
|
for (RCTModuleData *moduleData in _moduleDataByID) {
|
|
if ([moduleData.instance respondsToSelector:@selector(batchDidComplete)]) {
|
|
[moduleData dispatchBlock:^{
|
|
[moduleData.instance batchDidComplete];
|
|
}];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
RCTProfileBeginEvent();
|
|
|
|
RCTModuleData *moduleData = _moduleDataByID[moduleID];
|
|
if (RCT_DEBUG && !moduleData) {
|
|
RCTLogError(@"No module found for id '%zd'", moduleID);
|
|
return NO;
|
|
}
|
|
|
|
RCTModuleMethod *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) {
|
|
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, moduleData.name, params, exception);
|
|
if (!RCT_DEBUG && [exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
|
|
@throw exception;
|
|
}
|
|
}
|
|
|
|
RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{
|
|
@"module": NSStringFromClass(method.moduleClass),
|
|
@"method": method.JSMethodName,
|
|
@"selector": NSStringFromSelector(method.selector),
|
|
@"args": RCTJSONStringify(RCTNullIfNil(params), NULL),
|
|
});
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
|
|
{
|
|
RCTAssertJSThread();
|
|
RCTProfileBeginEvent();
|
|
|
|
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
|
for (RCTModuleData *moduleData in _frameUpdateObservers) {
|
|
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
|
|
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
|
|
RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];)
|
|
RCTProfileBeginFlowEvent();
|
|
|
|
[moduleData dispatchBlock:^{
|
|
RCTProfileEndFlowEvent();
|
|
RCTProfileBeginEvent();
|
|
[observer didUpdateFrame:frameUpdate];
|
|
RCTProfileEndEvent(name, @"objc_call,fps", nil);
|
|
}];
|
|
}
|
|
}
|
|
|
|
NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
|
|
|
|
RCT_IF_DEV(
|
|
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
|
|
|
|
for (NSDictionary *call in calls) {
|
|
_RCTProfileEndFlowEvent(call[@"call_id"]);
|
|
}
|
|
)
|
|
|
|
if (calls.count > 0) {
|
|
_scheduledCalls = [[NSMutableArray alloc] init];
|
|
_scheduledCallbacks = [[RCTSparseArray alloc] init];
|
|
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
|
method:@"processBatch"
|
|
arguments:@[[calls valueForKey:@"js_args"]]];
|
|
}
|
|
|
|
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self.perfStats.jsGraph onTick:displayLink.timestamp];
|
|
});
|
|
}
|
|
|
|
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
|
|
{
|
|
RCTAssertMainThread();
|
|
|
|
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
|
|
|
|
_modulesByName == nil ?: [self.perfStats.uiGraph onTick:displayLink.timestamp];
|
|
}
|
|
|
|
- (void)startProfiling
|
|
{
|
|
RCTAssertMainThread();
|
|
|
|
if (![_sourceURL.scheme isEqualToString:@"http"]) {
|
|
RCTLogError(@"To run the profiler you must be running from the dev server");
|
|
return;
|
|
}
|
|
|
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
|
RCTProfileInit(self);
|
|
}];
|
|
}
|
|
|
|
- (void)stopProfiling
|
|
{
|
|
RCTAssertMainThread();
|
|
|
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
|
NSString *log = RCTProfileEnd(self);
|
|
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _sourceURL.scheme, _sourceURL.host, _sourceURL.port];
|
|
NSURL *URL = [NSURL URLWithString:URLString];
|
|
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
|
|
URLRequest.HTTPMethod = @"POST";
|
|
[URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
|
NSURLSessionTask *task =
|
|
[[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
|
|
fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
|
|
completionHandler:
|
|
^(__unused NSData *data, __unused NSURLResponse *response, NSError *error) {
|
|
if (error) {
|
|
RCTLogError(@"%@", error.localizedDescription);
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[[[UIAlertView alloc] initWithTitle:@"Profile"
|
|
message:@"The profile has been generated, check the dev server log for instructions."
|
|
delegate:nil
|
|
cancelButtonTitle:@"OK"
|
|
otherButtonTitles:nil] show];
|
|
});
|
|
}
|
|
}];
|
|
|
|
[task resume];
|
|
}];
|
|
}
|
|
|
|
@end
|