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代码
- (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的更多相关文章
- iOS原生项目中集成React Native
1.本文的前提条件是,电脑上已经安装了CocoaPods,React Native相关环境. 2.使用Xcode新建一个工程.EmbedRNMeituan [图1] 3.使用CocoaPods安装Re ...
- 利用 Create React Native App 快速创建 React Native 应用
本文介绍的 Create-React-Native-App 是非常 Awesome 的工具,而其背后的 Expo 整个平台也让笔者感觉非常的不错.笔者目前公司是采用 APICloud 进行移动应用开发 ...
- React Native APP结构探索
APP结构探索 我在Github上找到了一个有登陆界面,能从网上获取新闻信息的开源APP,想来研究一下APP的结构. 附上原网址:我的第一个React Native App 具体来讲,就是研究一个复杂 ...
- React Native App设置&Android版发布
React Native系列 <逻辑性最强的React Native环境搭建与调试> <ReactNative开发工具有这一篇足矣> <解决React Native un ...
- [译] Facebook:我们是如何构建第一个跨平台的 React Native APP
英文原文(需FQ):https://code.facebook.com/posts/1189117404435352/ 早些时候,我们介绍过iOS版的React Native. React Nativ ...
- React Native & app demos
React Native & app demos https://github.com/ReactNativeNews/React-Native-Apps https://github.com ...
- 我的第一个React Native App
我用了三天时间实现了一个相对比较完整的React Native 新闻发布类型的示例.应用做得很简单,但大多React Native的组件都有用到,今天做一个分享(由于我电脑是Windows系统,所以只 ...
- 用CodePush在React Native App中做热更新
最近在学React Native,学到了CodePush热更新. 老师讲了两种实现的方法,现将其记录一下. 相比较原生开发,使用React Native开发App不仅能节约开发成本,还能做原生开发不能 ...
- 利用 Create React Native App 创建 React Native 应用
$ npm i -g create-react-native-app $ create-react-native-app my-project $ cd my-project $ npm start
- React Native 系列(一) -- JS入门知识
前言 本系列是基于React Native版本号0.44.3写的,最初学习React Native的时候,完全没有接触过React和JS,本文的目的是为了给那些JS和React小白提供一个快速入门,让 ...
随机推荐
- IIS-Server is too busy _解决方法
httpRuntime Server Too Busy 修改方法:修改服务器.net配置“machine.config"文件,该文件位于Windows系统目录下,如“C:\WINDOWS \Micro ...
- Hibernate5.2之原生SQL查询
Hibernate5.2之原生SQL查询 一. 介绍 在上一篇博客中笔者通过代码的形式给各位读者介绍了Hibernate中最重要的检索方式--HQL查询.在本博文中笔者将向各位读者介绍Hiberna ...
- C++模板元编程 - 挖新坑的时候探索到了模板元编程的新玩法
C++真是一门自由的语言,虽然糖没有C#那么多,但是你想要怎么写,想要实现什么,想要用某种编程范式或者语言特性,它都会提供. 开大数运算类的新坑的时候(又是坑),无意中需要解决一个需求:大数类需要分别 ...
- 通过 Code First 开发建立新数据库
必备条件 要完成本演练,需要安装 Visual Studio 2010 或 Visual Studio 2012. 如果使用的是 Visual Studio 2010,还需要安装 NuGet. 1.创 ...
- [EventBus源码解析] EventBus.post 方法详述
前情概要 上一篇blog我们了解了EventBus中register/unregister的过程,对EventBus如何实现观察者模式有了基本的认识.今天我们来看一下它是如何分发一个特定事件的,即po ...
- js调用java代码返回解决方案
版权声明:本文为楼主原创文章,未经楼主允许不得转载,如要转载请注明来源. 今天封装一个加密标签,遇到一个问题,我需要对页面上的数据调用java后台代码进行解密,而标签里只能通过js获取到数据,所以就遇 ...
- Libevent库 编译与使用
Libevent官网:http://libevent.org/ windows 7下编译: 编译环境: windows 7 + VS2010 (1)解压libevent到F:\libevent\lib ...
- Inside The C++ Object Model - 03
object Lessons 1.C++中布局以及存取时间上的的额外负担是由virtual引起的:virtual function.virtual base class.或是由于多继承引起的. 2.C ...
- mac上抓iphone数据包
iOS 5后,apple引入了RVI remote virtual interface的特性,它只需要将iOS设备使用USB数据线连接到mac上,然后使用rvictl工具以iOS设备的UDID为参数在 ...
- 常用freemarker使用文档
设置价格格式 <#setting number_format = "currency" /> <#assign price = 42 /> ${price} ...