mirror of
https://github.com/status-im/react-native.git
synced 2025-01-09 17:15:54 +00:00
855d411321
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
1038 lines
29 KiB
Objective-C
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
|