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代码

 - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;

B: 管理"bridge module"

 - (id)moduleForName:(NSString *)moduleName;
- (id)moduleForClass:(Class)moduleClass;

C: 创建 JavaScript 执行器

 - (void)initModules
{
......
_javaScriptExecutor = [self moduleForClass:self.executorClass];

1. 寻找起点-RCTRootView

在React-Native Based的工程中, 我们看到在AppDelegate.m文件中有以下代码:

   RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"AwesomeProject"
launchOptions:launchOptions]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [[UIViewController alloc] init];
rootViewController.view = rootView;

之所以可以使用JS来进行iOS App开发,RCTRootView类是可以进行探索其原因的起点。

2. RCTBridge

接下来浏览类RCTRootView源码,RCTRootView是UIView的子类,并且很简单。其中有一个属性:

 @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

 - (void)registerModules
{
RCTAssertMainThread(); // Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in self.moduleProvider ? self.moduleProvider() : nil) { // A
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
} // Instantiate modules
_moduleDataByID = [[NSMutableArray alloc] init];
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
for (Class moduleClass in RCTGetModuleClasses()) { // B
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); // Check if module instance has already been registered for this name
id<RCTBridgeModule> module = modulesByName[moduleName]; if (module) {
// Preregistered instances takes precedence, no questions asked
if (!preregisteredModules[moduleName]) {
// It's OK to have a name collision as long as the second instance is nil
RCTAssert([[moduleClass alloc] init] == nil,
@"Attempted to register RCTBridgeModule class %@ for the name "
"'%@', but name was already registered by class %@", moduleClass,
moduleName, [modulesByName[moduleName] class]);
}
if ([module class] != moduleClass) {
RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered "
"in the project, but name was already registered by class %@."
"That's fine if it's intentional - just letting you know.",
moduleClass, moduleName, [modulesByName[moduleName] class]);
}
} else {
// Module name hasn't been used before, so go ahead and instantiate
module = [[moduleClass alloc] init];
}
if (module) {
modulesByName[moduleName] = module;
}
} // Store modules
_modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName]; /**
* The executor is a bridge module, wait for it to be created and set it before
* any other module has access to the bridge
*/
_javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)]; // C
RCTLatestExecutor = _javaScriptExecutor; [_javaScriptExecutor setUp]; // Set bridge
for (id<RCTBridgeModule> module in _modulesByName.allValues) { // D
if ([module respondsToSelector:@selector(setBridge:)]) {
module.bridge = self;
} RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor
uid:@(_moduleDataByID.count)
instance:module];
[_moduleDataByID addObject:moduleData]; if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:moduleData];
}
}
// E
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules
object:self];
}

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

 - (void)initJS
{
RCTAssertMainThread(); // Inject module data into JS context
NSMutableDictionary *config = [[NSMutableDictionary alloc] init]; // A
for (RCTModuleData *moduleData in _moduleDataByID) {
config[moduleData.name] = moduleData.config;
}
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": config,
}, NULL);
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
callback:^(NSError *error) {
if (error) {
[[RCTRedBox sharedInstance] showError:error];
}
}]; NSURL *bundleURL = _parentBridge.bundleURL;
if (_javaScriptExecutor == nil) { /**
* HACK (tadeu): If it failed to connect to the debugger, set loading to NO
* so we can attempt to reload again.
*/
_loading = NO; } else if (!bundleURL) { // Allow testing without a script
dispatch_async(dispatch_get_main_queue(), ^{
_loading = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge
userInfo:@{ @"bridge": self }];
});
} else {
RCTProfileBeginEvent();
RCTPerformanceLoggerStart(RCTPLScriptDownload);
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; // B
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]); _loading = NO;
if (!self.isValid) {
return;
} static BOOL shouldDismiss = NO;
if (shouldDismiss) {
[[RCTRedBox sharedInstance] dismiss];
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shouldDismiss = YES;
}); RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = bundleURL;
sourceCodeModule.scriptText = script;
if (error) { NSArray *stack = [error userInfo][@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
withStack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
withDetails:[error localizedFailureReason]];
} NSDictionary *userInfo = @{@"bridge": self, @"error": error};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:_parentBridge
userInfo:userInfo]; } else { [self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) { // C

if (loadError) {
[[RCTRedBox sharedInstance] showError:loadError];
return;
} /**
* Register the display link to start sending js calls after everything
* is setup
*/
NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
[_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes]; // D
      // E
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge
userInfo:@{ @"bridge": self }];
}];
}
}];
}
}

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

 - (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
....... [self start]; // 1
}
return self;
}

2: 在start方法的最后,模块初始化完毕,并且(React Native App的)源码加载完毕后执行源码。

模块的初始化包含两个部分:

A) JavaScript 模块的初始化

B) Native 模块的初始化。方法 initModules 完成了 Native模块的初始化。

 - (void)start
{
......
// Synchronously initialize all native modules that cannot be loaded lazily
[self initModules];

......
dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{
RCTBatchedBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
dispatch_async(bridgeQueue, ^{
[weakSelf executeSourceCode:sourceCode]; // 2
});
}
});

3: executeSourceCode方法调用方法enqueueApplicationScript来执行(React Native App的)JavaScript源码。

 - (void)executeSourceCode:(NSData *)sourceCode
{
...... RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]];
sourceCodeModule.scriptURL = self.bundleURL;
sourceCodeModule.scriptData = sourceCode; [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) { // 3
...... // 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}];
});
}]; }

4: 方法enqueueApplicationScript 最终依赖RCTJSCExecutor类型的实例 _javaScriptExecutor

来执行(React Native App的)JavaScript源码。

 - (void)enqueueApplicationScript:(NSData *)script
url:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{ [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) { // 4

....... [_javaScriptExecutor flushedQueue:^(id json, NSError *error)
{ [self handleBuffer:json batchEnded:YES]; onComplete(error);
}];
}];
}

下一节来走读 类 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:

 /**
* Uses a JavaScriptCore context as the execution engine.
*/
@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)。

 @property (nonatomic, strong, readonly) Class moduleClass; // 1
@property (nonatomic, copy, readonly) NSString *name; // 2
 @property (nonatomic, copy, readonly) NSArray<id<RCTBridgeMethod>> *methods; // 3

@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance; // 4
 @property (nonatomic, strong, readonly) dispatch_queue_t methodQueue; // 5

@property (nonatomic, copy, readonly) NSArray *config; // 6

RCTModuleData的方法instance 会创建"bridge module"模块实例:

 - (id<RCTBridgeModule>)instance
{
[_instanceLock lock];
if (!_setupComplete) {
if (!_instance) {
_instance = [_moduleClass new];
}
// Bridge must be set before methodQueue is set up, as methodQueue
// initialization requires it (View Managers get their queue by calling
// self.bridge.uiManager.methodQueue)
[self setBridgeForInstance];
[self setUpMethodQueue];
[_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
_setupComplete = YES;
}
[_instanceLock unlock];
return _instance;
}

6. RCTJavaScriptLoader

从本地文件系统或者远程Server加载JavaScript。

 + (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComplete

该接口实现使用了 NSURLSessionDataTask, React Native需要 iOS 7.0+ 的系统

7. RCTSourceCode

RCTSourceCode抽象JavaScript源码数据, 包含属性:

scriptData 和 scriptURL
 @interface RCTSourceCode : NSObject <RCTBridgeModule> // E

 @property (nonatomic, copy) NSData *scriptData;
@property (nonatomic, copy) NSURL *scriptURL; @end

E: 关于 RCTBridgeModule接口 参见 iOS.ReactNative-3-about-viewmanager-uimanager-and-bridgemodule


Reference

1. React-Native: RCTBridge.m/h

iOS.ReactNative-2-bridge-and-react-native-app-execution的更多相关文章

  1. iOS原生项目中集成React Native

    1.本文的前提条件是,电脑上已经安装了CocoaPods,React Native相关环境. 2.使用Xcode新建一个工程.EmbedRNMeituan [图1] 3.使用CocoaPods安装Re ...

  2. 利用 Create React Native App 快速创建 React Native 应用

    本文介绍的 Create-React-Native-App 是非常 Awesome 的工具,而其背后的 Expo 整个平台也让笔者感觉非常的不错.笔者目前公司是采用 APICloud 进行移动应用开发 ...

  3. React Native APP结构探索

    APP结构探索 我在Github上找到了一个有登陆界面,能从网上获取新闻信息的开源APP,想来研究一下APP的结构. 附上原网址:我的第一个React Native App 具体来讲,就是研究一个复杂 ...

  4. React Native App设置&Android版发布

    React Native系列 <逻辑性最强的React Native环境搭建与调试> <ReactNative开发工具有这一篇足矣> <解决React Native un ...

  5. [译] Facebook:我们是如何构建第一个跨平台的 React Native APP

    英文原文(需FQ):https://code.facebook.com/posts/1189117404435352/ 早些时候,我们介绍过iOS版的React Native. React Nativ ...

  6. React Native & app demos

    React Native & app demos https://github.com/ReactNativeNews/React-Native-Apps https://github.com ...

  7. 我的第一个React Native App

    我用了三天时间实现了一个相对比较完整的React Native 新闻发布类型的示例.应用做得很简单,但大多React Native的组件都有用到,今天做一个分享(由于我电脑是Windows系统,所以只 ...

  8. 用CodePush在React Native App中做热更新

    最近在学React Native,学到了CodePush热更新. 老师讲了两种实现的方法,现将其记录一下. 相比较原生开发,使用React Native开发App不仅能节约开发成本,还能做原生开发不能 ...

  9. 利用 Create React Native App 创建 React Native 应用

    $ npm i -g create-react-native-app $ create-react-native-app my-project $ cd my-project $ npm start

  10. React Native 系列(一) -- JS入门知识

    前言 本系列是基于React Native版本号0.44.3写的,最初学习React Native的时候,完全没有接触过React和JS,本文的目的是为了给那些JS和React小白提供一个快速入门,让 ...

随机推荐

  1. linux下生成rsa密钥的方法

    首先生成密钥,用命令ssh-keygen –t rsa 运行后可以一直空格,生成密钥,id_rsa和id_rsa.pub文件 ,默认放在/root/.ssh/下,.ssh文件是隐藏的,要显示隐藏文件才 ...

  2. Angular(1)

    1.设计原则 1.YAGNI  不要把未来需求引入当前工程   2.KISS  keep it simple and stupid  语义化标记 合理注释 符合规定的命名 3.DRY(don't re ...

  3. mac与php环境

    一.目录 apache目录:/etc/apache2/ mysql目录:/usr/local/mysql/ 站点目录:/Library/WebServer/Documents/ 二.mac系统给文件夹 ...

  4. PRD

  5. 一张图教你搞定Mac App Store 应用安装包存储路径

    还在为找不到App Store 更新应用的安装文件发愁吗?是否有过多个人同时需要更新Xcode,都自己下载一次的痛苦经历? 大家都知道通过苹果服务器下载东西,确实难耐!AppStore 甚至都经常提示 ...

  6. unity3d 镜头随触屏移动

    js #pragma strict //用于绑定参照物对象 var target : Transform; //缩放系数 var distance = 10.0; //左右滑动移动速度 var xSp ...

  7. 同一台服务器启动多个driver负载机实例

    COSBench添加driver负载机 说明:Driver是COSBench测试工具中对负载机的一种标记,相当于loadrunner中的负载发生器. 在进行测试时,不管出于什么原因,我有时候就想单台服 ...

  8. WCF大文件传输

    WCF传输文件的时候可以设置每次文件的传输大小,如果是小文件的时候,可以很方便的将文件传递到服务端,但是如果文件比较大的话,就不可取了 遇到大文件的话可以采取分段传输的方式进行文件传输 思路: 1.客 ...

  9. Kettle6使用

    1.Kettle是一个开源的ETL(Extract-Transform-Load的缩写,即数据抽取.转换.装载的过程)项目,java编写,绿色无需安装 下载http://community.penta ...

  10. string和vector

    一.String对象 1.string s;      s.size(); //返回的是s中字符的个数,也是s的长度: //string对象最后没有加空字符 //size()返回的是string::s ...