mirror of
https://github.com/status-im/react-native.git
synced 2025-01-09 09:12:02 +00:00
19caaba1d5
Summary: * The dev support code moved into a `DevSupport` subspec, meaning that only if the subspec is specified in the user’s Podfile will the packager client, dev menu, etc be included. This is mainly done through checks for header availability. It also improves the weird situation where you had to specify the `RCTWebSocket` subspec if you wanted to be able to use the packager client during development. * I removed hardcoding the release version in the podspec on release, because the podspec still relies on `package.json` when evaluating, so there’s no real point in not also getting the version number from there. This should remove any requirement to perform maintenance of the OSS release script regarding the podspec. Closes https://github.com/facebook/react-native/pull/12602 Differential Revision: D4621021 Pulled By: ericvicenti fbshipit-source-id: 6c208371fc40ea607809a6ab05dd3714ed9980cf
1103 lines
35 KiB
Objective-C
1103 lines
35 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+Private.h"
|
|
#import "RCTBridge.h"
|
|
#import "RCTBridgeMethod.h"
|
|
#import "RCTConvert.h"
|
|
#import "RCTDisplayLink.h"
|
|
#import "RCTJSCExecutor.h"
|
|
#import "RCTJavaScriptLoader.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTModuleData.h"
|
|
#import "RCTPerformanceLogger.h"
|
|
#import "RCTProfile.h"
|
|
#import "RCTRedBox.h"
|
|
#import "RCTUtils.h"
|
|
|
|
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
|
|
#import "RCTDevLoadingView.h"
|
|
#endif
|
|
|
|
#define RCTAssertJSThread() \
|
|
RCTAssert(![NSStringFromClass([self->_javaScriptExecutor class]) isEqualToString:@"RCTJSCExecutor"] || \
|
|
[[[NSThread currentThread] name] isEqualToString:RCTJSCThreadName], \
|
|
@"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,
|
|
};
|
|
|
|
@implementation RCTBatchedBridge
|
|
{
|
|
BOOL _wasBatchActive;
|
|
NSMutableArray<dispatch_block_t> *_pendingCalls;
|
|
NSDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
|
|
NSArray<RCTModuleData *> *_moduleDataByID;
|
|
NSArray<Class> *_moduleClassesByID;
|
|
NSUInteger _modulesInitializedOnMainQueue;
|
|
RCTDisplayLink *_displayLink;
|
|
}
|
|
|
|
@synthesize flowID = _flowID;
|
|
@synthesize flowIDMap = _flowIDMap;
|
|
@synthesize flowIDMapLock = _flowIDMapLock;
|
|
@synthesize loading = _loading;
|
|
@synthesize valid = _valid;
|
|
@synthesize performanceLogger = _performanceLogger;
|
|
|
|
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
|
|
{
|
|
RCTAssertParam(bridge);
|
|
|
|
if (self = [super initWithDelegate:bridge.delegate
|
|
bundleURL:bridge.bundleURL
|
|
moduleProvider:bridge.moduleProvider
|
|
launchOptions:bridge.launchOptions]) {
|
|
_parentBridge = bridge;
|
|
_performanceLogger = [bridge performanceLogger];
|
|
|
|
RCTLogInfo(@"Initializing %@ (parent: %@, executor: %@)", self, bridge, [self executorClass]);
|
|
|
|
/**
|
|
* Set Initial State
|
|
*/
|
|
_valid = YES;
|
|
_loading = YES;
|
|
_pendingCalls = [NSMutableArray new];
|
|
_displayLink = [RCTDisplayLink new];
|
|
|
|
[RCTBridge setCurrentBridge:self];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
|
|
bundleURL:(NSURL *)bundleURL
|
|
moduleProvider:(RCTBridgeModuleProviderBlock)block
|
|
launchOptions:(NSDictionary *)launchOptions)
|
|
|
|
- (void)start
|
|
{
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName:RCTJavaScriptWillStartLoadingNotification
|
|
object:_parentBridge userInfo:@{@"bridge": self}];
|
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge setUp]", nil);
|
|
|
|
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, __unused int64_t sourceLength) {
|
|
if (error) {
|
|
RCTLogWarn(@"Failed to load source: %@", error);
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[weakSelf stopLoadingWithError:error];
|
|
});
|
|
}
|
|
|
|
sourceCode = source;
|
|
dispatch_group_leave(initModulesAndLoadSource);
|
|
} onProgress:^(RCTLoadingProgress *progressData) {
|
|
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
|
|
RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
|
|
[loadingView updateProgress:progressData];
|
|
#endif
|
|
}];
|
|
|
|
// Synchronously initialize all native modules that cannot be loaded lazily
|
|
[self initModulesWithDispatchGroup:initModulesAndLoadSource];
|
|
|
|
RCTPerformanceLogger *performanceLogger = self->_performanceLogger;
|
|
__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, ^{
|
|
[performanceLogger markStartForTag:RCTPLJSCExecutorSetup];
|
|
[weakSelf setUpExecutor];
|
|
[performanceLogger markStopForTag:RCTPLJSCExecutorSetup];
|
|
});
|
|
|
|
// Asynchronously gather the module config
|
|
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
|
|
if (weakSelf.valid) {
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge moduleConfig", nil);
|
|
[performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig];
|
|
config = [weakSelf moduleConfig];
|
|
[performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig];
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}
|
|
});
|
|
|
|
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.
|
|
[performanceLogger markStartForTag:RCTPLNativeModuleInjectConfig];
|
|
[weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
|
|
[performanceLogger markStopForTag:RCTPLNativeModuleInjectConfig];
|
|
if (error) {
|
|
RCTLogWarn(@"Failed to inject config: %@", error);
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[weakSelf stopLoadingWithError:error];
|
|
});
|
|
}
|
|
}];
|
|
dispatch_group_leave(initModulesAndLoadSource);
|
|
});
|
|
});
|
|
|
|
dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
|
|
RCTBatchedBridge *strongSelf = weakSelf;
|
|
if (sourceCode && strongSelf.loading) {
|
|
[strongSelf executeSourceCode:sourceCode];
|
|
}
|
|
});
|
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}
|
|
|
|
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
|
|
{
|
|
[_performanceLogger markStartForTag:RCTPLScriptDownload];
|
|
|
|
RCTPerformanceLogger *performanceLogger = _performanceLogger;
|
|
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSData *source, int64_t sourceLength) {
|
|
[performanceLogger markStopForTag:RCTPLScriptDownload];
|
|
[performanceLogger setValue:sourceLength forTag:RCTPLBundleSize];
|
|
_onSourceLoad(error, source, sourceLength);
|
|
};
|
|
|
|
if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) {
|
|
[self.delegate loadSourceForBridge:_parentBridge onProgress:onProgress onComplete:onSourceLoad];
|
|
} else if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) {
|
|
[self.delegate loadSourceForBridge:_parentBridge withBlock:onSourceLoad];
|
|
} else if (!self.bundleURL) {
|
|
NSError *error = RCTErrorWithMessage(@"No bundle URL present.\n\nMake sure you're running a packager " \
|
|
"server or have included a .jsbundle file in your application bundle.");
|
|
onSourceLoad(error, nil, 0);
|
|
} else {
|
|
[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error, NSData *source, int64_t sourceLength) {
|
|
if (error && [self.delegate respondsToSelector:@selector(fallbackSourceURLForBridge:)]) {
|
|
NSURL *fallbackURL = [self.delegate fallbackSourceURLForBridge:self->_parentBridge];
|
|
if (fallbackURL && ![fallbackURL isEqual:self.bundleURL]) {
|
|
RCTLogError(@"Failed to load bundle(%@) with error:(%@)", self.bundleURL, error.localizedDescription);
|
|
self.bundleURL = fallbackURL;
|
|
[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:onSourceLoad];
|
|
return;
|
|
}
|
|
}
|
|
onSourceLoad(error, source, sourceLength);
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (NSArray<Class> *)moduleClasses
|
|
{
|
|
if (RCT_DEBUG && _valid && _moduleClassesByID == nil) {
|
|
RCTLogError(@"Bridge modules have not yet been initialized. You may be "
|
|
"trying to access a module too early in the startup procedure.");
|
|
}
|
|
return _moduleClassesByID;
|
|
}
|
|
|
|
/**
|
|
* Used by RCTUIManager
|
|
*/
|
|
- (RCTModuleData *)moduleDataForName:(NSString *)moduleName
|
|
{
|
|
return _moduleDataByName[moduleName];
|
|
}
|
|
|
|
- (id)moduleForName:(NSString *)moduleName
|
|
{
|
|
return _moduleDataByName[moduleName].instance;
|
|
}
|
|
|
|
- (BOOL)moduleIsInitialized:(Class)moduleClass
|
|
{
|
|
return _moduleDataByName[RCTBridgeModuleNameForClass(moduleClass)].hasInstance;
|
|
}
|
|
|
|
- (NSArray *)configForModuleName:(NSString *)moduleName
|
|
{
|
|
RCTModuleData *moduleData = _moduleDataByName[moduleName];
|
|
if (moduleData) {
|
|
#if RCT_DEV
|
|
if ([self.delegate respondsToSelector:@selector(whitelistedModulesForBridge:)]) {
|
|
NSArray *whitelisted = [self.delegate whitelistedModulesForBridge:self];
|
|
RCTAssert(!whitelisted || [whitelisted containsObject:[moduleData moduleClass]],
|
|
@"Required config for %@, which was not whitelisted", moduleName);
|
|
}
|
|
#endif
|
|
}
|
|
return moduleData.config;
|
|
}
|
|
|
|
- (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
|
|
{
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge initModules]", nil);
|
|
[_performanceLogger markStartForTag:RCTPLNativeModuleInit];
|
|
|
|
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();
|
|
}
|
|
|
|
#if RCT_DEBUG
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
RCTVerifyAllModulesExported(extraModules);
|
|
});
|
|
#endif
|
|
|
|
NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
|
|
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
|
|
NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
|
|
|
|
// Set up moduleData for pre-initialized module instances
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"extraModules", nil);
|
|
for (id<RCTBridgeModule> module in extraModules) {
|
|
Class moduleClass = [module class];
|
|
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
|
|
|
|
if (RCT_DEBUG) {
|
|
// Check for name collisions between preregistered modules
|
|
RCTModuleData *moduleData = moduleDataByName[moduleName];
|
|
if (moduleData) {
|
|
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
|
|
"name '%@', but name was already registered by class %@",
|
|
moduleClass, moduleName, moduleData.moduleClass);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Instantiate moduleData container
|
|
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
|
|
bridge:self];
|
|
moduleDataByName[moduleName] = moduleData;
|
|
[moduleClassesByID addObject:moduleClass];
|
|
[moduleDataByID addObject:moduleData];
|
|
|
|
// Set executor instance
|
|
if (moduleClass == self.executorClass) {
|
|
_javaScriptExecutor = (id<RCTJavaScriptExecutor>)module;
|
|
}
|
|
}
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
|
|
// The executor is a bridge module, but we want it to be instantiated before
|
|
// any other module has access to the bridge, in case they need the JS thread.
|
|
// TODO: once we have more fine-grained control of init (t11106126) we can
|
|
// probably just replace this with [self moduleForClass:self.executorClass]
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"JavaScriptExecutor", nil);
|
|
if (!_javaScriptExecutor) {
|
|
id<RCTJavaScriptExecutor> executorModule = [self.executorClass new];
|
|
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:executorModule
|
|
bridge:self];
|
|
moduleDataByName[moduleData.name] = moduleData;
|
|
[moduleClassesByID addObject:self.executorClass];
|
|
[moduleDataByID addObject:moduleData];
|
|
|
|
// NOTE: _javaScriptExecutor is a weak reference
|
|
_javaScriptExecutor = executorModule;
|
|
}
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
|
|
// Set up moduleData for automatically-exported modules
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"ModuleData", nil);
|
|
for (Class moduleClass in RCTGetModuleClasses()) {
|
|
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
|
|
|
|
// Check for module name collisions
|
|
RCTModuleData *moduleData = moduleDataByName[moduleName];
|
|
if (moduleData) {
|
|
if (moduleData.hasInstance) {
|
|
// Existing module was preregistered, so it takes precedence
|
|
continue;
|
|
} else if ([moduleClass new] == nil) {
|
|
// The new module returned nil from init, so use the old module
|
|
continue;
|
|
} else if ([moduleData.moduleClass new] != nil) {
|
|
// Both modules were non-nil, so it's unclear which should take precedence
|
|
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
|
|
"name '%@', but name was already registered by class %@",
|
|
moduleClass, moduleName, moduleData.moduleClass);
|
|
}
|
|
}
|
|
|
|
// Instantiate moduleData (TODO: can we defer this until config generation?)
|
|
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
|
|
bridge:self];
|
|
moduleDataByName[moduleName] = moduleData;
|
|
[moduleClassesByID addObject:moduleClass];
|
|
[moduleDataByID addObject:moduleData];
|
|
}
|
|
|
|
// Store modules
|
|
_moduleDataByID = [moduleDataByID copy];
|
|
_moduleDataByName = [moduleDataByName copy];
|
|
_moduleClassesByID = [moduleClassesByID copy];
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
|
|
// Synchronously set up the pre-initialized modules
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"extraModules", nil);
|
|
for (RCTModuleData *moduleData in _moduleDataByID) {
|
|
if (moduleData.hasInstance &&
|
|
(!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
|
|
// Modules that were pre-initialized should ideally be set up before
|
|
// bridge init has finished, otherwise the caller may try to access the
|
|
// module directly rather than via `[bridge moduleForClass:]`, which won't
|
|
// trigger the lazy initialization process. If the module cannot safely be
|
|
// set up on the current thread, it will instead be async dispatched
|
|
// to the main thread to be set up in the loop below.
|
|
(void)[moduleData instance];
|
|
}
|
|
}
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
|
|
// From this point on, RCTDidInitializeModuleNotification notifications will
|
|
// be sent the first time a module is accessed.
|
|
_moduleSetupComplete = YES;
|
|
|
|
[self prepareModulesWithDispatchGroup:dispatchGroup];
|
|
|
|
[_performanceLogger markStopForTag:RCTPLNativeModuleInit];
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}
|
|
|
|
- (void)prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
|
|
{
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge prepareModulesWithDispatch]", nil);
|
|
|
|
NSArray<Class> *whitelistedModules = nil;
|
|
if ([self.delegate respondsToSelector:@selector(whitelistedModulesForBridge:)]) {
|
|
whitelistedModules = [self.delegate whitelistedModulesForBridge:self];
|
|
}
|
|
|
|
BOOL initializeImmediately = NO;
|
|
if (dispatchGroup == NULL) {
|
|
// If no dispatchGroup is passed in, we must prepare everything immediately.
|
|
// We better be on the right thread too.
|
|
RCTAssertMainQueue();
|
|
initializeImmediately = YES;
|
|
} else if ([self.delegate respondsToSelector:@selector(shouldBridgeInitializeNativeModulesSynchronously:)]) {
|
|
initializeImmediately = [self.delegate shouldBridgeInitializeNativeModulesSynchronously:self];
|
|
}
|
|
|
|
// Set up modules that require main thread init or constants export
|
|
for (RCTModuleData *moduleData in _moduleDataByID) {
|
|
if (whitelistedModules && ![whitelistedModules containsObject:[moduleData moduleClass]]) {
|
|
continue;
|
|
}
|
|
|
|
if (moduleData.requiresMainQueueSetup || moduleData.hasConstantsToExport) {
|
|
// Modules that need to be set up on the main thread cannot be initialized
|
|
// lazily when required without doing a dispatch_sync to the main thread,
|
|
// which can result in deadlock. To avoid this, we initialize all of these
|
|
// modules on the main thread in parallel with loading the JS code, so
|
|
// they will already be available before they are ever required.
|
|
dispatch_block_t block = ^{
|
|
if (self.valid) {
|
|
[self->_performanceLogger appendStartForTag:RCTPLNativeModuleMainThread];
|
|
(void)[moduleData instance];
|
|
[moduleData gatherConstants];
|
|
[self->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread];
|
|
}
|
|
};
|
|
|
|
if (initializeImmediately && RCTIsMainQueue()) {
|
|
block();
|
|
} else {
|
|
// We've already checked that dispatchGroup is non-null, but this satisifies the
|
|
// Xcode analyzer
|
|
if (dispatchGroup) {
|
|
dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
|
|
}
|
|
}
|
|
_modulesInitializedOnMainQueue++;
|
|
}
|
|
}
|
|
|
|
[_performanceLogger setValue:_modulesInitializedOnMainQueue forTag:RCTPLNativeModuleMainThreadUsesCount];
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}
|
|
|
|
- (void)whitelistedModulesDidChange
|
|
{
|
|
RCTAssertMainQueue();
|
|
[self prepareModulesWithDispatchGroup:NULL];
|
|
}
|
|
|
|
- (void)setUpExecutor
|
|
{
|
|
[_javaScriptExecutor setUp];
|
|
}
|
|
|
|
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
|
|
withModuleData:(RCTModuleData *)moduleData
|
|
{
|
|
[_displayLink registerModuleForFrameUpdates:module withModuleData:moduleData];
|
|
}
|
|
|
|
- (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)injectJSONConfiguration:(NSString *)configJSON
|
|
onComplete:(void (^)(NSError *))onComplete
|
|
{
|
|
if (!_valid) {
|
|
return;
|
|
}
|
|
|
|
[_javaScriptExecutor injectJSONText:configJSON
|
|
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
|
|
callback:onComplete];
|
|
}
|
|
|
|
- (void)executeSourceCode:(NSData *)sourceCode
|
|
{
|
|
if (!_valid || !_javaScriptExecutor) {
|
|
return;
|
|
}
|
|
|
|
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
|
|
if (!self->_valid) {
|
|
return;
|
|
}
|
|
|
|
if (loadError) {
|
|
RCTLogWarn(@"Failed to execute source code: %@", [loadError localizedDescription]);
|
|
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 = [self->_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
|
|
[self->_displayLink addToRunLoop:targetRunLoop];
|
|
|
|
// Log metrics about native requires during the bridge startup.
|
|
uint64_t nativeRequiresCount = [self->_performanceLogger valueForTag:RCTPLRAMNativeRequiresCount];
|
|
[self->_performanceLogger setValue:nativeRequiresCount forTag:RCTPLRAMStartupNativeRequiresCount];
|
|
uint64_t nativeRequires = [self->_performanceLogger valueForTag:RCTPLRAMNativeRequires];
|
|
[self->_performanceLogger setValue:nativeRequires forTag:RCTPLRAMStartupNativeRequires];
|
|
|
|
[self->_performanceLogger markStopForTag:RCTPLBridgeStartup];
|
|
|
|
// Perform the notification on the main thread, so we can't run into
|
|
// timing issues with RCTRootView
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName:RCTJavaScriptDidLoadNotification
|
|
object:self->_parentBridge userInfo:@{@"bridge": self}];
|
|
});
|
|
|
|
[self _flushPendingCalls];
|
|
}];
|
|
|
|
#if RCT_DEV
|
|
if ([RCTGetURLQueryParam(self.bundleURL, @"hot") boolValue]) {
|
|
NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash
|
|
NSString *host = self.bundleURL.host;
|
|
NSNumber *port = self.bundleURL.port;
|
|
[self enqueueJSCall:@"HMRClient"
|
|
method:@"enable"
|
|
args:@[@"ios", path, host, RCTNullIfNil(port)]
|
|
completion:NULL];
|
|
}
|
|
#endif
|
|
}
|
|
|
|
- (void)_flushPendingCalls
|
|
{
|
|
RCTAssertJSThread();
|
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"Processing pendingCalls", @{ @"count": @(_pendingCalls.count) });
|
|
_loading = NO;
|
|
NSArray *pendingCalls = _pendingCalls;
|
|
_pendingCalls = nil;
|
|
for (dispatch_block_t call in pendingCalls) {
|
|
call();
|
|
}
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}
|
|
|
|
- (void)stopLoadingWithError:(NSError *)error
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
if (!_valid || !_loading) {
|
|
return;
|
|
}
|
|
|
|
_loading = NO;
|
|
[_javaScriptExecutor invalidate];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName:RCTJavaScriptDidFailToLoadNotification
|
|
object:_parentBridge userInfo:@{@"bridge": self, @"error": error}];
|
|
|
|
if ([error userInfo][RCTJSStackTraceKey]) {
|
|
[self.redBox showErrorMessage:[error localizedDescription]
|
|
withStack:[error userInfo][RCTJSStackTraceKey]];
|
|
}
|
|
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];
|
|
}
|
|
|
|
- (void)requestReload
|
|
{
|
|
[_parentBridge requestReload];
|
|
}
|
|
|
|
- (Class)executorClass
|
|
{
|
|
return _parentBridge.executorClass ?: [RCTJSCExecutor class];
|
|
}
|
|
|
|
- (void)setExecutorClass:(Class)executorClass
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
_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) {
|
|
RCTProfileBeginFlowEvent();
|
|
RCTAssert(_javaScriptExecutor != nil, @"Need JS executor to schedule JS work");
|
|
|
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
|
RCTProfileEndFlowEvent();
|
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge dispatchBlock", @{ @"loading": @(self.loading) });
|
|
|
|
if (self.loading) {
|
|
RCTAssert(self->_pendingCalls != nil, @"Can't add pending call, bridge is no longer loading");
|
|
[self->_pendingCalls addObject:block];
|
|
} else {
|
|
block();
|
|
}
|
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}];
|
|
} else if (queue) {
|
|
dispatch_async(queue, block);
|
|
}
|
|
}
|
|
|
|
#pragma mark - RCTInvalidating
|
|
|
|
- (void)invalidate
|
|
{
|
|
if (!_valid) {
|
|
return;
|
|
}
|
|
|
|
RCTAssertMainQueue();
|
|
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 _moduleDataByID) {
|
|
// Be careful when grabbing an instance here, we don't want to instantiate
|
|
// any modules just to invalidate them.
|
|
id<RCTBridgeModule> instance = nil;
|
|
if ([moduleData hasInstance]) {
|
|
instance = moduleData.instance;
|
|
}
|
|
|
|
if (instance == _javaScriptExecutor) {
|
|
continue;
|
|
}
|
|
|
|
if ([instance respondsToSelector:@selector(invalidate)]) {
|
|
dispatch_group_enter(group);
|
|
[self dispatchBlock:^{
|
|
[(id<RCTInvalidating>)instance invalidate];
|
|
dispatch_group_leave(group);
|
|
} queue:moduleData.methodQueue];
|
|
}
|
|
[moduleData invalidate];
|
|
}
|
|
|
|
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
|
[self->_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
|
[self->_displayLink invalidate];
|
|
self->_displayLink = nil;
|
|
|
|
[self->_javaScriptExecutor invalidate];
|
|
self->_javaScriptExecutor = nil;
|
|
|
|
if (RCTProfileIsProfiling()) {
|
|
RCTProfileUnhookModules(self);
|
|
}
|
|
|
|
self->_moduleDataByName = nil;
|
|
self->_moduleDataByID = nil;
|
|
self->_moduleClassesByID = nil;
|
|
self->_pendingCalls = nil;
|
|
|
|
if (self->_flowIDMap != NULL) {
|
|
CFRelease(self->_flowIDMap);
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)logMessage:(NSString *)message level:(NSString *)level
|
|
{
|
|
if (RCT_DEBUG && [_javaScriptExecutor isValid]) {
|
|
[self enqueueJSCall:@"RCTLog"
|
|
method:@"logIfNoNativeHook"
|
|
args:@[level, message]
|
|
completion:NULL];
|
|
}
|
|
}
|
|
|
|
#pragma mark - RCTBridge methods
|
|
|
|
/**
|
|
* Public. Can be invoked from any thread.
|
|
*/
|
|
- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
|
|
{
|
|
/**
|
|
* AnyThread
|
|
*/
|
|
if (!_valid) {
|
|
return;
|
|
}
|
|
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTBatchedBridge enqueueJSCall:]", nil);
|
|
__weak __typeof(self) weakSelf = self;
|
|
[self dispatchBlock:^{
|
|
[weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]];
|
|
if (completion) {
|
|
completion();
|
|
}
|
|
} queue:RCTJSThread];
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}
|
|
|
|
/**
|
|
* Called by RCTModuleMethod from any thread.
|
|
*/
|
|
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
|
|
{
|
|
/**
|
|
* AnyThread
|
|
*/
|
|
if (!_valid) {
|
|
return;
|
|
}
|
|
|
|
__weak __typeof(self) weakSelf = self;
|
|
[self dispatchBlock:^{
|
|
[weakSelf _actuallyInvokeCallback:cbID arguments:args];
|
|
} queue:RCTJSThread];
|
|
}
|
|
|
|
/**
|
|
* JS thread only
|
|
*/
|
|
- (JSValue *)callFunctionOnModule:(NSString *)module
|
|
method:(NSString *)method
|
|
arguments:(NSArray *)arguments
|
|
error:(NSError **)error
|
|
{
|
|
RCTJSCExecutor *jsExecutor = (RCTJSCExecutor *)_javaScriptExecutor;
|
|
if (![jsExecutor isKindOfClass:[RCTJSCExecutor class]]) {
|
|
RCTLogWarn(@"FBReactBridgeJSExecutor is only supported when running in JSC");
|
|
return nil;
|
|
}
|
|
|
|
__block JSValue *jsResult = nil;
|
|
|
|
RCTAssertJSThread();
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"callFunctionOnModule", (@{ @"module": module, @"method": method }));
|
|
[jsExecutor callFunctionOnModule:module
|
|
method:method
|
|
arguments:arguments ?: @[]
|
|
jsValueCallback:^(JSValue *result, NSError *jsError) {
|
|
if (error) {
|
|
*error = jsError;
|
|
}
|
|
|
|
JSValue *length = result[@"length"];
|
|
RCTAssert([length isNumber] && [length toUInt32] == 2,
|
|
@"Return value of a callFunction must be an array of size 2");
|
|
|
|
jsResult = [result valueAtIndex:0];
|
|
|
|
NSArray *nativeModuleCalls = [[result valueAtIndex:1] toArray];
|
|
[self handleBuffer:nativeModuleCalls batchEnded:YES];
|
|
}];
|
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call");
|
|
|
|
return jsResult;
|
|
}
|
|
|
|
|
|
/**
|
|
* Private hack to support `setTimeout(fn, 0)`
|
|
*/
|
|
- (void)_immediatelyCallTimer:(NSNumber *)timer
|
|
{
|
|
RCTAssertJSThread();
|
|
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:^{
|
|
[self _actuallyInvokeAndProcessModule:@"JSTimersExecution"
|
|
method:@"callTimers"
|
|
arguments:@[@[timer]]];
|
|
}];
|
|
}
|
|
|
|
- (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(RCTProfileTagAlways, @"FetchApplicationScriptCallbacks", nil);
|
|
[self->_javaScriptExecutor flushedQueue:^(id json, NSError *error)
|
|
{
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,init");
|
|
[self handleBuffer:json batchEnded:YES];
|
|
onComplete(error);
|
|
}];
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Payload Generation
|
|
|
|
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
|
|
method:(NSString *)method
|
|
arguments:(NSArray *)args
|
|
{
|
|
RCTAssertJSThread();
|
|
|
|
__weak __typeof(self) weakSelf = self;
|
|
[_javaScriptExecutor callFunctionOnModule:module
|
|
method:method
|
|
arguments:args
|
|
callback:^(id json, NSError *error) {
|
|
[weakSelf _processResponse:json error:error];
|
|
}];
|
|
}
|
|
|
|
- (void)_actuallyInvokeCallback:(NSNumber *)cbID
|
|
arguments:(NSArray *)args
|
|
{
|
|
RCTAssertJSThread();
|
|
|
|
__weak __typeof(self) weakSelf = self;
|
|
[_javaScriptExecutor invokeCallbackID:cbID
|
|
arguments:args
|
|
callback:^(id json, NSError *error) {
|
|
[weakSelf _processResponse:json error:error];
|
|
}];
|
|
}
|
|
|
|
- (void)_processResponse:(id)json error:(NSError *)error
|
|
{
|
|
if (error) {
|
|
if ([error userInfo][RCTJSStackTraceKey]) {
|
|
[self.redBox showErrorMessage:[error localizedDescription]
|
|
withStack:[error userInfo][RCTJSStackTraceKey]];
|
|
}
|
|
RCTFatal(error);
|
|
}
|
|
|
|
if (!_valid) {
|
|
return;
|
|
}
|
|
[self handleBuffer:json batchEnded:YES];
|
|
}
|
|
|
|
#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 = self->_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();
|
|
|
|
NSOrderedSet *calls = [buckets objectForKey:queue];
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTBatchedBridge handleBuffer:]", (@{
|
|
@"calls": @(calls.count),
|
|
}));
|
|
|
|
@autoreleasepool {
|
|
for (NSNumber *indexObj in calls) {
|
|
NSUInteger index = indexObj.unsignedIntegerValue;
|
|
#if RCT_PROFILE
|
|
if (RCT_DEV && callID != -1 && self->_flowIDMap != NULL && RCTProfileIsProfiling()) {
|
|
[self.flowIDMapLock lock];
|
|
NSUInteger newFlowID = (NSUInteger)CFDictionaryGetValue(self->_flowIDMap, (const void *)(self->_flowID + index));
|
|
_RCTProfileEndFlowEvent(newFlowID);
|
|
CFDictionaryRemoveValue(self->_flowIDMap, (const void *)(self->_flowID + index));
|
|
[self.flowIDMapLock unlock];
|
|
}
|
|
#endif
|
|
[self callNativeModule:[moduleIDs[index] integerValue]
|
|
method:[methodIDs[index] integerValue]
|
|
params:paramsArrays[index]];
|
|
}
|
|
}
|
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call,dispatch_async");
|
|
};
|
|
|
|
[self dispatchBlock:block queue:queue];
|
|
}
|
|
|
|
_flowID = callID;
|
|
}
|
|
|
|
- (void)partialBatchDidFlush
|
|
{
|
|
for (RCTModuleData *moduleData in _moduleDataByID) {
|
|
if (moduleData.hasInstance && 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.hasInstance && moduleData.implementsBatchDidComplete) {
|
|
[self dispatchBlock:^{
|
|
[moduleData.instance batchDidComplete];
|
|
} queue:moduleData.methodQueue];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (id)callNativeModule:(NSUInteger)moduleID
|
|
method:(NSUInteger)methodID
|
|
params:(NSArray *)params
|
|
{
|
|
if (!_valid) {
|
|
return nil;
|
|
}
|
|
|
|
RCTModuleData *moduleData = _moduleDataByID[moduleID];
|
|
if (RCT_DEBUG && !moduleData) {
|
|
RCTLogError(@"No module found for id '%zd'", moduleID);
|
|
return nil;
|
|
}
|
|
|
|
id<RCTBridgeMethod> method = moduleData.methods[methodID];
|
|
if (RCT_DEBUG && !method) {
|
|
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, moduleData.name);
|
|
return nil;
|
|
}
|
|
|
|
@try {
|
|
return [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 nil;
|
|
}
|
|
}
|
|
|
|
- (void)startProfiling
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
|
RCTProfileInit(self);
|
|
}];
|
|
}
|
|
|
|
- (void)stopProfiling:(void (^)(NSData *))callback
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
|
RCTProfileEnd(self, ^(NSString *log) {
|
|
NSData *logData = [log dataUsingEncoding:NSUTF8StringEncoding];
|
|
callback(logData);
|
|
});
|
|
}];
|
|
}
|
|
|
|
- (BOOL)isBatchActive
|
|
{
|
|
return _wasBatchActive;
|
|
}
|
|
|
|
@end
|