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. IIS-Server is too busy _解决方法

    httpRuntime Server Too Busy 修改方法:修改服务器.net配置“machine.config"文件,该文件位于Windows系统目录下,如“C:\WINDOWS \Micro ...

  2. Hibernate5.2之原生SQL查询

    Hibernate5.2之原生SQL查询 一. 介绍  在上一篇博客中笔者通过代码的形式给各位读者介绍了Hibernate中最重要的检索方式--HQL查询.在本博文中笔者将向各位读者介绍Hiberna ...

  3. C++模板元编程 - 挖新坑的时候探索到了模板元编程的新玩法

    C++真是一门自由的语言,虽然糖没有C#那么多,但是你想要怎么写,想要实现什么,想要用某种编程范式或者语言特性,它都会提供. 开大数运算类的新坑的时候(又是坑),无意中需要解决一个需求:大数类需要分别 ...

  4. 通过 Code First 开发建立新数据库

    必备条件 要完成本演练,需要安装 Visual Studio 2010 或 Visual Studio 2012. 如果使用的是 Visual Studio 2010,还需要安装 NuGet. 1.创 ...

  5. [EventBus源码解析] EventBus.post 方法详述

    前情概要 上一篇blog我们了解了EventBus中register/unregister的过程,对EventBus如何实现观察者模式有了基本的认识.今天我们来看一下它是如何分发一个特定事件的,即po ...

  6. js调用java代码返回解决方案

    版权声明:本文为楼主原创文章,未经楼主允许不得转载,如要转载请注明来源. 今天封装一个加密标签,遇到一个问题,我需要对页面上的数据调用java后台代码进行解密,而标签里只能通过js获取到数据,所以就遇 ...

  7. Libevent库 编译与使用

    Libevent官网:http://libevent.org/ windows 7下编译: 编译环境: windows 7 + VS2010 (1)解压libevent到F:\libevent\lib ...

  8. Inside The C++ Object Model - 03

    object Lessons 1.C++中布局以及存取时间上的的额外负担是由virtual引起的:virtual function.virtual base class.或是由于多继承引起的. 2.C ...

  9. mac上抓iphone数据包

    iOS 5后,apple引入了RVI remote virtual interface的特性,它只需要将iOS设备使用USB数据线连接到mac上,然后使用rvictl工具以iOS设备的UDID为参数在 ...

  10. 常用freemarker使用文档

    设置价格格式 <#setting number_format = "currency" /> <#assign price = 42 /> ${price} ...