/** * 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 "RCTBridge.h" #import "RCTBridge+Private.h" #import #import "RCTBridge+JavaScriptCore.h" #import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "RCTLog.h" #import "RCTModuleData.h" #import "RCTPerformanceLogger.h" #import "RCTProfile.h" #import "RCTReloadCommand.h" #import "RCTUtils.h" NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; NSString *const RCTDidInitializeModuleNotification = @"RCTDidInitializeModuleNotification"; static NSMutableArray *RCTModuleClasses; NSArray *RCTGetModuleClasses(void) { return RCTModuleClasses; } void RCTFBQuickPerformanceLoggerConfigureHooks(__unused JSGlobalContextRef ctx) { } /** * Register the given class as a bridge module. All modules must be registered * prior to the first bridge initialization. */ void RCTRegisterModule(Class); void RCTRegisterModule(Class moduleClass) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ RCTModuleClasses = [NSMutableArray new]; }); RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)], @"%@ does not conform to the RCTBridgeModule protocol", moduleClass); // Register module [RCTModuleClasses addObject:moduleClass]; } /** * This function returns the module name for a given class. */ NSString *RCTBridgeModuleNameForClass(Class cls) { #if RCT_DEBUG RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)], @"Bridge module `%@` does not conform to RCTBridgeModule", cls); #endif NSString *name = [cls moduleName]; if (name.length == 0) { name = NSStringFromClass(cls); } if ([name hasPrefix:@"RK"]) { name = [name substringFromIndex:2]; } else if ([name hasPrefix:@"RCT"]) { name = [name substringFromIndex:3]; } return name; } #if RCT_DEBUG void RCTVerifyAllModulesExported(NSArray *extraModules) { // Check for unexported modules unsigned int classCount; Class *classes = objc_copyClassList(&classCount); NSMutableSet *moduleClasses = [NSMutableSet new]; [moduleClasses addObjectsFromArray:RCTGetModuleClasses()]; [moduleClasses addObjectsFromArray:[extraModules valueForKeyPath:@"class"]]; for (unsigned int i = 0; i < classCount; i++) { Class cls = classes[i]; Class superclass = cls; while (superclass) { if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule))) { if ([moduleClasses containsObject:cls]) { break; } // Verify it's not a super-class of one of our moduleClasses BOOL isModuleSuperClass = NO; for (Class moduleClass in moduleClasses) { if ([moduleClass isSubclassOfClass:cls]) { isModuleSuperClass = YES; break; } } if (isModuleSuperClass) { break; } RCTLogWarn(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", cls); break; } superclass = class_getSuperclass(superclass); } } free(classes); } #endif @interface RCTBridge () @end @implementation RCTBridge { NSURL *_delegateBundleURL; } dispatch_queue_t RCTJSThread; + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // Set up JS thread RCTJSThread = (id)kCFNull; }); } static RCTBridge *RCTCurrentBridgeInstance = nil; /** * The last current active bridge instance. This is set automatically whenever * the bridge is accessed. It can be useful for static functions or singletons * that need to access the bridge for purposes such as logging, but should not * be relied upon to return any particular instance, due to race conditions. */ + (instancetype)currentBridge { return RCTCurrentBridgeInstance; } + (void)setCurrentBridge:(RCTBridge *)currentBridge { RCTCurrentBridgeInstance = currentBridge; } - (instancetype)initWithDelegate:(id)delegate launchOptions:(NSDictionary *)launchOptions { return [self initWithDelegate:delegate bundleURL:nil moduleProvider:nil launchOptions:launchOptions]; } - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleProvider:(RCTBridgeModuleListProvider)block launchOptions:(NSDictionary *)launchOptions { return [self initWithDelegate:nil bundleURL:bundleURL moduleProvider:block launchOptions:launchOptions]; } - (instancetype)initWithDelegate:(id)delegate bundleURL:(NSURL *)bundleURL moduleProvider:(RCTBridgeModuleListProvider)block launchOptions:(NSDictionary *)launchOptions { if (self = [super init]) { _delegate = delegate; _bundleURL = bundleURL; _moduleProvider = block; _launchOptions = [launchOptions copy]; [self setUp]; } return self; } RCT_NOT_IMPLEMENTED(- (instancetype)init) - (void)dealloc { /** * This runs only on the main thread, but crashes the subclass * RCTAssertMainQueue(); */ [self invalidate]; } - (void)didReceiveReloadCommand { [self reload]; } - (NSArray *)moduleClasses { return self.batchedBridge.moduleClasses; } - (id)moduleForName:(NSString *)moduleName { return [self.batchedBridge moduleForName:moduleName]; } - (id)moduleForClass:(Class)moduleClass { return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)]; } - (NSArray *)modulesConformingToProtocol:(Protocol *)protocol { NSMutableArray *modules = [NSMutableArray new]; for (Class moduleClass in self.moduleClasses) { if ([moduleClass conformsToProtocol:protocol]) { id module = [self moduleForClass:moduleClass]; if (module) { [modules addObject:module]; } } } return [modules copy]; } - (BOOL)moduleIsInitialized:(Class)moduleClass { return [self.batchedBridge moduleIsInitialized:moduleClass]; } - (void)whitelistedModulesDidChange { [self.batchedBridge whitelistedModulesDidChange]; } - (void)reload { /** * Any thread */ dispatch_async(dispatch_get_main_queue(), ^{ [self invalidate]; [self setUp]; }); } - (void)requestReload { [self reload]; } - (Class)bridgeClass { // In order to facilitate switching between bridges with only build // file changes, this uses reflection to check which bridges are // available. This is a short-term hack until RCTBatchedBridge is // removed. Class batchedBridgeClass = objc_lookUpClass("RCTBatchedBridge"); Class cxxBridgeClass = objc_lookUpClass("RCTCxxBridge"); Class implClass = nil; if ([self.delegate respondsToSelector:@selector(shouldBridgeUseCxxBridge:)]) { if ([self.delegate shouldBridgeUseCxxBridge:self]) { implClass = cxxBridgeClass; } else { implClass = batchedBridgeClass; } } else if (batchedBridgeClass != nil) { implClass = batchedBridgeClass; } else if (cxxBridgeClass != nil) { implClass = cxxBridgeClass; } RCTAssert(implClass != nil, @"No bridge implementation is available, giving up."); return implClass; } - (void)setUp { RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBridge setUp]", nil); _performanceLogger = [RCTPerformanceLogger new]; [_performanceLogger markStartForTag:RCTPLBridgeStartup]; [_performanceLogger markStartForTag:RCTPLTTI]; Class bridgeClass = self.bridgeClass; #if RCT_DEV RCTExecuteOnMainQueue(^{ RCTRegisterReloadCommandListener(self); }); #endif // Only update bundleURL from delegate if delegate bundleURL has changed NSURL *previousDelegateURL = _delegateBundleURL; _delegateBundleURL = [self.delegate sourceURLForBridge:self]; if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) { _bundleURL = _delegateBundleURL; } // Sanitize the bundle URL _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString]; self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self]; [self.batchedBridge start]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } - (BOOL)isLoading { return self.batchedBridge.loading; } - (BOOL)isValid { return self.batchedBridge.valid; } - (BOOL)isBatchActive { return [_batchedBridge isBatchActive]; } - (void)invalidate { RCTBridge *batchedBridge = self.batchedBridge; self.batchedBridge = nil; if (batchedBridge) { RCTExecuteOnMainQueue(^{ [batchedBridge invalidate]; }); } } - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args { NSArray *ids = [moduleDotMethod componentsSeparatedByString:@"."]; NSString *module = ids[0]; NSString *method = ids[1]; [self enqueueJSCall:module method:method args:args completion:NULL]; } - (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion { [self.batchedBridge enqueueJSCall:module method:method args:args completion:completion]; } - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args { [self.batchedBridge enqueueCallback:cbID args:args]; } - (JSValue *)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)arguments error:(NSError **)error { return [self.batchedBridge callFunctionOnModule:module method:method arguments:arguments error:error]; } @end @implementation RCTBridge (JavaScriptCore) - (JSContext *)jsContext { return [self.batchedBridge jsContext]; } - (JSGlobalContextRef)jsContextRef { return [self.batchedBridge jsContextRef]; } @end