iOS.ReactNative-2-bridge-and-react-native-app-execution
Bridge and React Native App Execution
基于0.18.1
Async batched bridge used to communicate with the JavaScript application.
分析Objective-C和JavaScript的通信机制。
Bridge承担以下工作(或者提供接口):
A: 执行JavaScript代码
1 - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
B: 管理"bridge module"
1 - (id)moduleForName:(NSString *)moduleName; 2 - (id)moduleForClass:(Class)moduleClass;
C: 创建 JavaScript 执行器
1 - (void)initModules 2 { 3 ...... 4 _javaScriptExecutor = [self moduleForClass:self.executorClass];
1. 寻找起点-RCTRootView
在React-Native Based的工程中, 我们看到在AppDelegate.m文件中有以下代码:
1 RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 2 moduleName:@"AwesomeProject" 3 launchOptions:launchOptions]; 4 5 self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 6 UIViewController *rootViewController = [[UIViewController alloc] init]; 7 rootViewController.view = rootView;
之所以可以使用JS来进行iOS App开发,RCTRootView类是可以进行探索其原因的起点。
2. RCTBridge
接下来浏览类RCTRootView源码,RCTRootView是UIView的子类,并且很简单。其中有一个属性:
1 @property (nonatomic, strong, readonly) RCTBridge *bridge;
通读类RCTBridge的代码,只有很少的200~300行。但是发现类RCTBatchedBridge继承自类RCTBridge。
类RCTBatchedBridge是Bridge模块的一个私有类,只被在类RCTBridge中被使用。这样设计使得接口和繁复的实现
分离。
3. RCTBatchedBridge
TODO: Rewrite this section to match the modification in version 0.18.1
观察类RCTBatchedBridge的initiailizer,发现对接口的'initJS'的调用。在这里终于和JS '发生关系'。
- (instancetype)initWithParentBridge:(RCTBridge *)bridge { // 省略多行代码 // ...... // ...... /** * Initialize and register bridge modules *before* adding the display link * so we don't have threading issues */ [self registerModules]; // A /** * Start the application script */ [self initJS]; // B } return self; }
在查看initJS方法的代码之前,我们先来关注比较重要的方法 registerModules。
3.0 Module是什么东西?
Module 在React Native中实际上是 可以被 JavaScript 代码调用的模块, 实现了接口RCTBridgeModule的类。
Module 包含有 Native类型, JavaScript源码类型。
A): Native类型:
由Objective-C来实现相应的功能,并将接口提供给JavaScript代码调用。
B): JavaScript源码类型:
也就是用JavaScript写的React Native App。这个类型的Module由 RCTSourceCode类 来代表。
3.1 registerModules 方法
TODO: Rewrite this section to match the modification in version 0.18.1
1 - (void)registerModules 2 { 3 RCTAssertMainThread(); 4 5 // Register passed-in module instances 6 NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; 7 for (id<RCTBridgeModule> module in self.moduleProvider ? self.moduleProvider() : nil) { // A 8 preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module; 9 } 10 11 // Instantiate modules 12 _moduleDataByID = [[NSMutableArray alloc] init]; 13 NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy]; 14 for (Class moduleClass in RCTGetModuleClasses()) { // B 15 NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); 16 17 // Check if module instance has already been registered for this name 18 id<RCTBridgeModule> module = modulesByName[moduleName]; 19 20 if (module) { 21 // Preregistered instances takes precedence, no questions asked 22 if (!preregisteredModules[moduleName]) { 23 // It's OK to have a name collision as long as the second instance is nil 24 RCTAssert([[moduleClass alloc] init] == nil, 25 @"Attempted to register RCTBridgeModule class %@ for the name " 26 "'%@', but name was already registered by class %@", moduleClass, 27 moduleName, [modulesByName[moduleName] class]); 28 } 29 if ([module class] != moduleClass) { 30 RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered " 31 "in the project, but name was already registered by class %@." 32 "That's fine if it's intentional - just letting you know.", 33 moduleClass, moduleName, [modulesByName[moduleName] class]); 34 } 35 } else { 36 // Module name hasn't been used before, so go ahead and instantiate 37 module = [[moduleClass alloc] init]; 38 } 39 if (module) { 40 modulesByName[moduleName] = module; 41 } 42 } 43 44 // Store modules 45 _modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName]; 46 47 /** 48 * The executor is a bridge module, wait for it to be created and set it before 49 * any other module has access to the bridge 50 */ 51 _javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)]; // C 52 RCTLatestExecutor = _javaScriptExecutor; 53 54 [_javaScriptExecutor setUp]; 55 56 // Set bridge 57 for (id<RCTBridgeModule> module in _modulesByName.allValues) { // D 58 if ([module respondsToSelector:@selector(setBridge:)]) { 59 module.bridge = self; 60 } 61 62 RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor 63 uid:@(_moduleDataByID.count) 64 instance:module]; 65 [_moduleDataByID addObject:moduleData]; 66 67 if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { 68 [_frameUpdateObservers addObject:moduleData]; 69 } 70 } 71 // E 72 [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules 73 object:self]; 74 }
A): A部分用来注册外部传递进来的Module。由于RCTBridge创建RCTBatchedBridge对象时,传入的参数导致
属性 self.moduleProvider 的值为nil,故我们先跳过这部分,直接跳到B部分。
B): B部分的循环是将加载的ModuleClass进行注册,注册到成员变量 '_modulesByName'中。
(其中的 RCTGetModuleClasses()和 RCTBridgeModuleNameForClass()
参见 iOS.ReactNative-3-about-viewmanager-uimanager-and-bridgemodule 中的说明)
C): 从'_modulesByName'中获取javaScriptExecutor,JS Executor是关键所在,JS Executor是执行JS代码的。
在 initJS 方法中会用到。
D): 为ModuleObject(模块对象/模块实例, 或者简称: 模块)设置bridge以及ModuleData(模块元数据),最后将实现接口RCTFrameUpdateObserver
的模块对象添加到'_frameUpdateObservers' 中。
E): 发送通知, NativeModules已创建完毕。TODO: 该通知的观察者做了哪些工作?
3.2 initJS 方法
TODO: Rewrite this section to match the modification in version 0.18.1
1 - (void)initJS 2 { 3 RCTAssertMainThread(); 4 5 // Inject module data into JS context 6 NSMutableDictionary *config = [[NSMutableDictionary alloc] init]; // A 7 for (RCTModuleData *moduleData in _moduleDataByID) { 8 config[moduleData.name] = moduleData.config; 9 } 10 NSString *configJSON = RCTJSONStringify(@{ 11 @"remoteModuleConfig": config, 12 }, NULL); 13 [_javaScriptExecutor injectJSONText:configJSON 14 asGlobalObjectNamed:@"__fbBatchedBridgeConfig" 15 callback:^(NSError *error) { 16 if (error) { 17 [[RCTRedBox sharedInstance] showError:error]; 18 } 19 }]; 20 21 NSURL *bundleURL = _parentBridge.bundleURL; 22 if (_javaScriptExecutor == nil) { 23 24 /** 25 * HACK (tadeu): If it failed to connect to the debugger, set loading to NO 26 * so we can attempt to reload again. 27 */ 28 _loading = NO; 29 30 } else if (!bundleURL) { 31 32 // Allow testing without a script 33 dispatch_async(dispatch_get_main_queue(), ^{ 34 _loading = NO; 35 [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification 36 object:_parentBridge 37 userInfo:@{ @"bridge": self }]; 38 }); 39 } else { 40 41 RCTProfileBeginEvent(); 42 RCTPerformanceLoggerStart(RCTPLScriptDownload); 43 RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; // B 44 [loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) { 45 RCTPerformanceLoggerEnd(RCTPLScriptDownload); 46 RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]); 47 48 _loading = NO; 49 if (!self.isValid) { 50 return; 51 } 52 53 static BOOL shouldDismiss = NO; 54 if (shouldDismiss) { 55 [[RCTRedBox sharedInstance] dismiss]; 56 } 57 static dispatch_once_t onceToken; 58 dispatch_once(&onceToken, ^{ 59 shouldDismiss = YES; 60 }); 61 62 RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; 63 sourceCodeModule.scriptURL = bundleURL; 64 sourceCodeModule.scriptText = script; 65 if (error) { 66 67 NSArray *stack = [error userInfo][@"stack"]; 68 if (stack) { 69 [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] 70 withStack:stack]; 71 } else { 72 [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] 73 withDetails:[error localizedFailureReason]]; 74 } 75 76 NSDictionary *userInfo = @{@"bridge": self, @"error": error}; 77 [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification 78 object:_parentBridge 79 userInfo:userInfo]; 80 81 } else { 82 83 [self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) { // C 84 85 if (loadError) { 86 [[RCTRedBox sharedInstance] showError:loadError]; 87 return; 88 } 89 90 /** 91 * Register the display link to start sending js calls after everything 92 * is setup 93 */ 94 NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop]; 95 [_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes]; // D 96 // E 97 [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification 98 object:_parentBridge 99 userInfo:@{ @"bridge": self }]; 100 }]; 101 } 102 }]; 103 } 104 }
A): 将每个ModuleObject 元数据中的config 注册到 JS Executor中。(config 参见 5. RCTModuleData)
B): 拉取JS Bundler, 可以将JS Bundler看作JS代码的'包'。
C): 执行Application JS code。(参见 4. JS Executor)
D): 将'_jsDisplayLink'添加到runloop中。'_jsDisplayLink'周期性的触发的工作是什么?
E): 发送通知 RCTJavaScriptDidLoadNotification。该通知的观察者进行了哪些处理?
到此,焦点会集中到JS Executor上面,接下来进行JS Executor的代码阅读。
3.3 invalidate 方法
TODO: Rewrite this section to match the modification in version 0.18.1
3.4 React Native App源码的执行
3.4.1 执行步骤
1: RCTBatchedBridge类的init方法中调用start方法来启动React Native App
1 - (instancetype)initWithParentBridge:(RCTBridge *)bridge 2 { 3 ....... 4 5 [self start]; // 1 6 } 7 return self; 8 }
2: 在start方法的最后,模块初始化完毕,并且(React Native App的)源码加载完毕后执行源码。
模块的初始化包含两个部分:
A) JavaScript 模块的初始化
B) Native 模块的初始化。方法 initModules 完成了 Native模块的初始化。
1 - (void)start 2 { 3 ...... 4 // Synchronously initialize all native modules that cannot be loaded lazily 5 [self initModules]; 6 7 ...... 8 dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{ 9 RCTBatchedBridge *strongSelf = weakSelf; 10 if (sourceCode && strongSelf.loading) { 11 dispatch_async(bridgeQueue, ^{ 12 [weakSelf executeSourceCode:sourceCode]; // 2 13 }); 14 } 15 });
3: executeSourceCode方法调用方法enqueueApplicationScript来执行(React Native App的)JavaScript源码。
1 - (void)executeSourceCode:(NSData *)sourceCode 2 { 3 ...... 4 5 RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]]; 6 sourceCodeModule.scriptURL = self.bundleURL; 7 sourceCodeModule.scriptData = sourceCode; 8 9 [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) { // 3 10 ...... 11 12 // Register the display link to start sending js calls after everything is setup 13 NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop]; 14 [_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes]; 15 16 // Perform the state update and notification on the main thread, so we can't run into 17 // timing issues with RCTRootView 18 dispatch_async(dispatch_get_main_queue(), ^{ 19 [self didFinishLoading]; 20 [[NSNotificationCenter defaultCenter] 21 postNotificationName:RCTJavaScriptDidLoadNotification 22 object:_parentBridge userInfo:@{@"bridge": self}]; 23 }); 24 }]; 25 26 }
4: 方法enqueueApplicationScript 最终依赖RCTJSCExecutor类型的实例 _javaScriptExecutor
来执行(React Native App的)JavaScript源码。
1 - (void)enqueueApplicationScript:(NSData *)script 2 url:(NSURL *)url 3 onComplete:(RCTJavaScriptCompleteBlock)onComplete 4 { 5 6 [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) { // 4 7 8 ....... 9 10 [_javaScriptExecutor flushedQueue:^(id json, NSError *error) 11 { 12 13 [self handleBuffer:json batchEnded:YES]; 14 15 onComplete(error); 16 }]; 17 }]; 18 }
下一节来走读 类 RCTJSCExecutor 和 接口RCTJavaScriptExecutor。
4. JS Executor
TODO: Rewrite this section to match the modification in version 0.18.1
4.0 接口RCTJavaScriptExecutor
接口RCTJavaScriptExecutor定义了JS Executor需要实现的接口。在React中提供了两个JS Executor的实现,
在React/Executors Group中:RCTWebViewExecutor、RCTContextExecutor。
WebSocket中也有一个实现: RCTWebSocketExecutor。
下面是接口RCTJavaScriptExecutor的方法声明:
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error); typedef void (^RCTJavaScriptCallback)(id json, NSError *error); /** * Abstracts away a JavaScript execution context - we may be running code in a * web view (for debugging purposes), or may be running code in a `JSContext`. */ @protocol RCTJavaScriptExecutor <RCTInvalidating, RCTBridgeModule> /** * Used to set up the executor after the bridge has been fully initialized. * Do any expensive setup in this method instead of `-init`. */ - (void)setUp; /** * Executes given method with arguments on JS thread and calls the given callback * with JSValue and JSContext as a result of the JS module call. */ - (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete; /** * Runs an application script, and notifies of the script load being complete via `onComplete`. */ - (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)sourceURL onComplete:(RCTJavaScriptCompleteBlock)onComplete;
// 将由script表示的JavaScript脚本代表的object以objectName注册为全局变量。 - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete; /** * Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async` * on the main queue if the executor doesn't own a thread. */ - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block; @optional /** * Special case for Timers + ContextExecutor - instead of the default * if jsthread then call else dispatch call on jsthread * ensure the call is made async on the jsthread */ - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block; @end
4.1 RCTJSCExecutor类
RCTJSCExecutor实现了接口RCTJavaScriptExecutor:
1 /** 2 * Uses a JavaScriptCore context as the execution engine. 3 */ 4 @interface RCTJSCExecutor : NSObject <RCTJavaScriptExecutor>
5. RCTModuleData
RCTModuleData实例保存关于RCTBridgeModule实例的数据,这些数据包含: "bridge module"模块的类(1),
"bridge module"模块在Javascript中名字(2), "bridge module"模块导出到JavaScript中的 Method(3),
"bridge module"模块实例(4), the module method dispatch queue(5), "bridge module"模块的配置信息(6)。
1 @property (nonatomic, strong, readonly) Class moduleClass; // 1 2 @property (nonatomic, copy, readonly) NSString *name; // 2
1 @property (nonatomic, copy, readonly) NSArray<id<RCTBridgeMethod>> *methods; // 3 2 3 @property (nonatomic, strong, readonly) id<RCTBridgeModule> instance; // 4
1 @property (nonatomic, strong, readonly) dispatch_queue_t methodQueue; // 5 2 3 @property (nonatomic, copy, readonly) NSArray *config; // 6
RCTModuleData的方法instance 会创建"bridge module"模块实例:
1 - (id<RCTBridgeModule>)instance 2 { 3 [_instanceLock lock]; 4 if (!_setupComplete) { 5 if (!_instance) { 6 _instance = [_moduleClass new]; 7 } 8 // Bridge must be set before methodQueue is set up, as methodQueue 9 // initialization requires it (View Managers get their queue by calling 10 // self.bridge.uiManager.methodQueue) 11 [self setBridgeForInstance]; 12 [self setUpMethodQueue]; 13 [_bridge registerModuleForFrameUpdates:_instance withModuleData:self]; 14 _setupComplete = YES; 15 } 16 [_instanceLock unlock]; 17 return _instance; 18 }
6. RCTJavaScriptLoader
从本地文件系统或者远程Server加载JavaScript。
1 + (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComplete
该接口实现使用了 NSURLSessionDataTask, React Native需要 iOS 7.0+ 的系统。
7. RCTSourceCode
RCTSourceCode抽象JavaScript源码数据, 包含属性:
scriptData 和 scriptURL
1 @interface RCTSourceCode : NSObject <RCTBridgeModule> // E 2 3 @property (nonatomic, copy) NSData *scriptData; 4 @property (nonatomic, copy) NSURL *scriptURL; 5 6 @end
E: 关于 RCTBridgeModule接口 参见 iOS.ReactNative-3-about-viewmanager-uimanager-and-bridgemodule
Reference
1. React-Native: RCTBridge.m/h