抛砖引玉(帮你更好的去理解怎么产生的 能做什么)
砖一、动态配置
由于 AppStore 审核周期的限制,如何动态的更改 app 成为了永恒的话题。无论采用何种方式,我们的流程总是可以归结为以下三部曲:“从 Server 获取配置 –> 解析 –> 执行native代码”。
很多时候,我们自觉或者不自觉的利用 JSON 文件实现动态配置的效果,它的核心流程是:
- 通过 HTTP 请求获取 JSON 格式的配置文件。
- 配置文件中标记了每一个元素的属性,比如位置,颜色,图片 URL 等。
- 解析完 JSON 后,我们调用 Objective-C 的代码,完成 UI 控件的渲染。
通过这种方法,我们实现了在后台配置 app 的展示样式。从本质上来说,移动端和服务端约定了一套协议,但是协议内容严重依赖于应用内要展示的内容,不利于拓展。也就是说,如果业务要求频繁的增加或修改页面,这套协议很难应付。
最重要的是,JSON 只是一种数据交换的格式,说白了,我们就是在解析文本数据。这就意味着它只适合提供一些配置信息,而不方便提供逻辑信息。举个例子,我们从后台可以配置颜色,位置等信息,但如果想要控制 app 内的业务逻辑,就非常复杂了。
记住,我们只是在解析字符串,它完全不具备运行和调试的能力。
砖二、React
背景
不妨暂时抛弃移动端的烦恼,来看看前端的“新玩意”。
作为前端小白,我以前对前端的理解是这样的:
- 用 HTML 创建 DOM,构建整个网页的布局、结构
- 用 CSS 控制 DOM 的样式,比如字体、字号、颜色、居中等
- 用 JavaScript 接受用户事件,动态的操控 DOM
在这三者的配合下,几乎所有页面上的功能都能实现。但也有比较不爽地方,比如我想动态修改一个按钮的文字,我需要这样写:
<button type="button" id="button" onclick="onClick()">old button</button>
<script>
function onClick() {
document.getElementById('button').innerHTML='new button';
}
</script>
可以看到,在 HTML 和 JavaScript 代码中,id
和 onclick
事件触发的函数必须完全对应,否则就无法正确的响应事件。如果想知道一个 HTML 标签会如何被响应,我们还得跑去 JavaScript 代码中查找,这种原始的配置方式让我觉得非常不爽。
初识 React
随着 FaceBook 推出了 React 框架,这个问题得到了大幅度改善。我们可以把一组相关的 HTML 标签,也就是 app 内的 UI 控件,封装进一个组件(Component)中,我从阮一峰的 React 教程中摘录了一段代码:
var MyComponent = React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div>
<input type="text" ref="myTextInput" />
<input type="button" value="Focus the text input" onClick={this.handleClick} />
</div>
);
}
});
如果你想问:“为什么 JavaScript 代码里面出现了 HTML 的语法”,那么恭喜你已经初步体会到 React 的奥妙了。这种语法被称为 JSX,它是一种 JavaScript 语法拓展。JSX 允许我们写 HTML 标签或 React 标签,它们终将被转换成原生的 JavaScript 并创建 DOM。React是采用JSX语法,一种JS语法糖,方便快速开发
在 React 框架中,除了可以用 JavaScript 写 HTML 以外,我们甚至可以写 CSS,这在后面的例子中可以看到。
理解 React
前端界总是喜欢创造新的概念,仿佛谁说的名词更晦涩,谁的水平就越高。如果你和当时的我一样,听到 React 这个概念一脸懵逼的话,只要记住以下定义即可:
React 是一套可以用简洁的语法高效绘制 DOM 的框架
上文已经解释过了何谓“简洁的语法”,因为我们可以暂时放下 HTML 和 CSS,只关心如何用 JavaScript 构造页面。
所谓的“高效”,是因为 React 独创了 Virtual DOM 机制。Virtual DOM 是一个存在于内存中的 JavaScript 对象,它与 DOM 是一一对应的关系,也就是说只要有 Virtual DOM,我们就能渲染出 DOM。
当界面发生变化时,得益于高效的 DOM Diff 算法,我们能够知道 Virtual DOM 的变化,从而高效的改动 DOM,避免了重新绘制 DOM。
当然,React 并不是前端开发的全部。从之前的描述也能看出,它专注于 UI 部分,对应到 MVC 结构中就是 View 层。要想实现完整的 MVC 架构,还需要 Model 和 Controller 的结构。在前端开发时,我们可以采用 Flux 和 Redux 架构,它们并非框架(Library),而是和 MVC 一样都是一种架构设计(Architecture)。
如果不从事前端开发,就不用深入的掌握 Flux 和 Redux 架构,但理解这一套体系结构对于后面理解 React Native 非常重要。
React Native
分别介绍完了移动端和前端的背景知识后,主角——React Native 终于要登场了
融合
前面我们介绍了移动端通过 JSON 文件传递信息的不足之处:只能传递配置信息,无法表达逻辑。从本质上讲,这是因为 JSON 毕竟只是纯文本,它缺乏像编程语言那样的运行能力。
而 React 在前端取得突破性成功以后,JavaScript 布道者们开始试图一统三端。他们利用了移动平台能够运行 JavaScript 代码的能力,并且发挥了 JavaScript 不仅仅可以传递配置信息,还可以表达逻辑信息的优点。
当痛点遇上特点,两者一拍即合,于是乎:
一个基于 JavaScript,具备动态配置能力,面向前端开发者的移动端开发框架,React Native,诞生了!
看到了么,这是一个面向前端开发者的框架。它的宗旨是让前端开发者像用 React 写网页那样,用 React Native 写移动端应用。这就是为什么 React Native 自称:
Learn once,Write anywhere!
而非很多跨平台语言,项目所说的:
Write once, Run anywhere!
React Native 希望前端开发者学习完 React 后,能够用同样的语法、工具等,分别开发安卓和 iOS 平台的应用并且不用一行原生代码。
如果用一个词概括 React Native,那就是:Native 版本的 React。
原理概述
接下来我以 iOS 平台为例,简单的解释一下 React Native 的原理。
首先要明白的一点是,即使使用了 React Native,我们依然需要 UIKit 等框架,调用的是 Objective-C 代码。总之,JavaScript 只是辅助,它只是提供了配置信息和逻辑的处理结果。React Native 与 Hybrid 完全没有关系,它只不过是以 JavaScript 的形式告诉 Objective-C 该执行什么代码。
其次,React Native 能够运行起来,全靠 Objective-C 和 JavaScript 的交互。对于没有接触过 JavaScript 的人来说,非常有必要理解 JavaScript 代码如何被执行。
我们知道 C 系列的语言,经过编译,链接等操作后,会得到一个二进制格式的可执行文,所谓的运行程序,其实是运行这个二进制程序。
而 JavaScript 是一种脚本语言,它不会经过编译、链接等操作,而是在运行时才动态的进行词法、语法分析,生成抽象语法树(AST)和字节码,然后由解释器负责执行或者使用 JIT 将字节码转化为机器码再执行。整个流程由 JavaScript 引擎负责完成。
苹果提供了一个叫做 JavaScript Core 的框架,这是一个 JavaScript 引擎。通过下面这段代码可以简单的感受一下 Objective-C 如何调用 JavaScript 代码:
JSContext *context = [[JSContext alloc] init];
JSValue *jsVal = [context evaluateScript:@"21+7"];
int iVal = [jsVal toInt32];
这里的 JSContext
指的是 JavaScript 代码的运行环境,通过 evaluateScript
即可执行 JavaScript 代码并获取返回结果。
JavaScript 是一种单线程的语言,它不具备自运行的能力,因此总是被动调用。很多介绍 React Native 的文章都会提到 “JavaScript 线程” 的概念,实际上,它表示的是 Objective-C 创建了一个单独的线程,这个线程只用于执行 JavaScript 代码,而且 JavaScript 代码只会在这个线程中执行
React Native 怎么做到以 JS 的形式告诉 OC 该执行什么代码 ?
抛出问题:(JS不知道 OC 有哪些方法可以调用)由于 JavaScript Core 是一个面向 Objective-C 的框架,在 Objective-C 这一端,我们对 JavaScript 上下文知根知底,可以很容易的获取到对象,方法等各种信息,当然也包括调用 JavaScript 函数。真正复杂的问题在于,JavaScript 不知道 Objective-C 有哪些方法可以调用
React Native 解决这个问题的方案是在 Objective-C 和 JavaScript 两端都保存了一份配置表,里面标记了所有 Objective-C 暴露给 JavaScript 的模块和方法。这样,无论是哪一方调用另一方的方法,实际上传递的数据只有 ModuleId
、MethodId
和 Arguments
这三个元素,它们分别表示类、方法和方法参数,当 Objective-C 接收到这三个值后,就可以通过 runtime 确定唯一要调用的是哪个函数,然后调用这个函数。
再次重申,上述解决方案只是一个抽象概念,可能与实际的解决方案有微小差异,比如实际上 Objective-C 这一端,并没有直接保存这个模块配置表。
抛出问题:(JS 代码调用 OC 之后,如何在 OC 的代码中,回调执行 JS 代码)说到函数互调,那么就不得不提到回调了。对于 Objective-C 来说,执行完 JavaScript 代码再执行 Objective-C 回调毫无难度,难点依然在于 JavaScript 代码调用 Objective-C 之后,如何在 Objective-C 的代码中,回调执行 JavaScript 代码。
目前 React Native 的做法是:在 JavaScript 调用 Objective-C 代码时,注册要回调的 Block,并且把 BlockId
作为参数发送给 Objective-C,Objective-C 收到参数时会创建 Block,调用完 Objective-C 函数后就会执行这个刚刚创建的 Block。Objective-C 会向 Block 中传入参数和 BlockId
,然后在 Block 内部调用 JavaScript 的方法,随后 JavaScript 查找到当时注册的 Block 并执行。
React Native 源码分析
深入理解 React Native 的工作原理,必须知道要阅读初始化阶段和方法调用阶段。
一、初始化阶段
一个不含 Objective-C 代码的项目留给我们的唯一线索就是位于 AppDelegate
文件中的代码
用户能看到的一切内容都来源于这个 RootView
,所有的初始化工作也都在这个方法内完成
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
//在创建 RootView 之前,初始化方法里面 实际上先创建了一个 Bridge 对象。
//它是 Objective-C 与 JavaScript 交互的桥梁,后续的方法交互完全依赖于它,而整个初始化过程的最终目的其实也就是创建这个桥梁对象
//初始化方法的核心是里面的 setUp 方法,而 setUp 方法的主要任务则是创建BatchedBridge。并执行start 方法 [self.batchedBridge start]; start中有五个步骤
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"hello"
initialProperties:nil
launchOptions:launchOptions];
start中有五个步骤
-
加载 JavaScript 源码 (作用:方便oc以后注入和执行)
//通过BundleURL加载JS源码
//JavaScript 加载进内存中,对于一个空的项目来说,所有的 JavaScript 代码大约占用 1.5 Mb 的内存空间需要说明的是,在这一步中,JSX 代码已经被转化成原生的 JavaScript 代码
[self loadSource:^(NSError *error, NSData *source, __unused int64_t sourceLength) {
if (error) {
RCTLogWarn(@"Failed to load source: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
} sourceCode = source;
dispatch_group_leave(initModulesAndLoadSource);
} onProgress:^(RCTLoadingProgress *progressData) {
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
[loadingView updateProgress:progressData];
#endif
}]; -
初始化模块信息(标记oc暴漏给js模块的方法 方便后面把这些模块信息注入 js端 让js 获取所有类的名字)
// Synchronously initialize all native modules that cannot be loaded lazily
[self initModulesWithDispatchGroup:initModulesAndLoadSource];在方法
initModulesWithDispatchGroup:
主要任务是找到所有需要暴露给 JavaScript 的类。每一个需要暴露给 JavaScript 的类(也称为 Module,以下不作区分)都会标记一个宏:RCT_EXPORT_MODULE
,这个宏的具体实现并不复杂:#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }这样,这个类在
load
方法中就会调用RCTRegisterModule
方法注册自己:void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
}); [RCTModuleClasses addObject:moduleClass];
}因此,React Native 可以通过
RCTModuleClasses
拿到所有暴露给 JavaScript 的类。下一步操作是遍历这个数组,然后生成RCTModuleData
对象:for (Class moduleClass in RCTGetModuleClasses()) {
RCTModuleData *moduleData = [[RCTModuleData alloc]initWithModuleClass:moduleClass bridge:self];
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}可以想见,
RCTModuleData
对象是模块配置表的主要组成部分。如果把模块配置表想象成一个数组,那么每一个元素就是一个RCTModuleData
对象。这个对象保存了 Module 的名字,常量等基本信息,最重要的属性是一个数组,保存了所有需要暴露给 JavaScript 的方法。
Objective-C 管理模块配置表的逻辑是:Bridge 持有一个数组,数组中保存了所有的模块的
RCTModuleData
对象。只要给定ModuleId
和MethodId
就可以唯一确定要调用的方法。- (NSArray<id<RCTBridgeMethod>> *)methods{
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount); // 获取方法列表
for (unsigned int i = ; i < methodCount; i++) {
RCTModuleMethod *moduleMethod = /* 创建 method */
[_methods addObject:moduleMethod];
}
}
return _methods;
} -
初始化 JavaScript 代码的执行器,即
RCTJSCExecutor
对象(作用:oc执行js代码用 定时询问js 转成原生的API)一般情况下,Objective-C 会定时、主动的调用
handleBuffer
方法//Objective-C 会定时、主动的调用 handleBuffer 方法询问js 有没有要调用我的消息啊
// 每个一段时间发生一次:
//OC:嘿,JS,有没有要调用我的方法呀?
//JS:有的,我已经放倒 MessageQueue 里面了, MessageQueue给你 自己通过runtime去里面取出来用吧。
//如果MessageQueue消息队列中有等待 Objective-C 处理的逻辑,而且 OC 超过 5ms 都没有来取走,那么 JavaScript 就会主动调用 Objective-C 的方法 [self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){
[self->_bridge handleBuffer:calls batchEnded:NO];
}];JavaScript 除了把调用信息放到 MessageQueue 中等待 Objective-C 来取以外,也可以主动调用 Objective-C 的方法
if (global.nativeFlushQueueImmediate && now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) { global.nativeFlushQueueImmediate(this._queue); // 调用 OC 的代码
}然而由于卡顿或某些特殊原因,Objective-C 并不能总是保证能够准时的清空 MessageQueue,这就是为什么 JavaScript 也会在一定时间后主动的调用 Objective-C 的方法。查看上面 JavaScript 的代码可以发现,这个等待时间是 5ms。
请牢牢记住这个 5ms,它告诉我们 JavaScript 与 Objective-C 的交互是存在一定开销的,不然就不会等待而是每次都立刻发起请求。其次,这个时间开销大约是毫秒级的,不会比 5ms 小太多,否则等待这么久就意义不大了
-
将OC模块列表写入 JavaScript 端 (作用:让 JS 获取所有模块的名字 方便JS返回messageQueue给OC)
这一步的操作就是为了让 JavaScript 获取所有模块的名字
查看源码可以发现,Objective-C 把 config 字符串设置成 JavaScript 的一个全局变量,名字叫做:__fbBatchedBridgeConfig
//之后OC 会将该返回的config 字符串 写入 JavaScript 端 设置成 JavaScript 的一个全局变量
- (NSString *)moduleConfig{
NSMutableArray<NSArray *> *config = [NSMutableArray new];
for (RCTModuleData *moduleData in _moduleDataByID) {
[config addObject:@[moduleData.name]];
}
} -
执行 JavaScript 源码
代码已经加载进了内存,该做的配置也已经完成,只要把 JavaScript 代码运行一遍即可
至此,JavaScript 和 Objective-C 都具备了向对方交互的能力,准备工作也就全部完成了
二、方法调用
在 React Native 中,Objective-C 和 JavaScript 的交互都是通过传递 ModuleId
、MethodId
和 Arguments
进行的。以下是分情况讨论:
1.OC调用JS
JavaScript 代码总是在一个单独的线程上面调用,它的实际含义是 Objective-C 会在单独的线程上运行 JavaScript 代码:
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
if ([NSThread currentThread] != _javaScriptThread) {
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
} else {
block();
}
}
OC 点用 JS的代码如下
- (void)_executeJSCall:(NSString *)method
arguments:(NSArray *)arguments
callback:(RCTJavaScriptCallback)onComplete{
[self executeBlockOnJavaScriptQueue:^{
// 获取 contextJSRef、methodJSRef、moduleJSRef
resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, arguments.count, jsArgs, &errorJSRef);
objcValue = /*resultJSRef 转换成 Objective-C 类型*/
onComplete(objcValue, nil);
}];
}
需要注意的是,这个函数名是我们要调用 JavaScript 的中转函数名,比如 callFunctionReturnFlushedQueue
。也就是说它的作用其实是处理参数,而非真正要调用的 JavaScript 函数。这个中转函数接收到的参数包含了 ModuleId
、MethodId
和 Arguments
,然后由中转函数查找自己的模块配置表,找到真正要调用的 JavaScript 函数。
实际使用 (OC调用JS)
//这里的 Name 和 Body 参数分别表示要调用的 JavaScript 的函数名和参数。
[_bridge.eventDispatcher sendAppEventWithName:@"greeted"
body:@{ @"name": @"nmae"}];
2.JS调用OC
如前文所述,JavaScript 会解析出方法的 ModuleId
、MethodId
和 Arguments
并放入到 MessageQueue 中,等待 Objective-C 主动拿走,或者超时后主动发送给 Objective-C。
Objective-C 负责处理调用的方法是 handleBuffer
,它的参数是一个含有四个元素的数组,每个元素也都是一个数组,分别存放了 ModuleId
、MethodId
、Params
,第四个元素目测用处不大。
函数内部在每一次方调用中调用 _handleRequestNumber:moduleID:methodID:params
方法。,通过查找模块配置表找出要调用的方法,并通过 runtime 动态的调用:
[method invokeWithBridge:self module:moduleData.instance arguments:params];
在这个方法中,有一个很关键的方法:processMethodSignature
,它会根据 JavaScript 的 CallbackId 创建一个 Block,并且在调用完函数后执行这个 Block。
React Native 优缺点分析
经过一长篇的讨论,其实 React Native 的优缺点已经不难分析了,这里简单总结一下:
优点
- 复用了 React 的思想,有利于前端开发者涉足移动端。
- 能够利用 JavaScript 动态更新的特性,快速迭代。
- 相比于原生平台,开发速度更快,相比于 Hybrid 框架,性能更好。
缺点
- 做不到
Write once, Run everywhere
,也就是说开发者依然需要为 iOS 和 Android 平台提供两套不同的代码,比如参考官方文档可以发现不少组件和API都区分了 Android 和 iOS 版本。即使是共用组件,也会有平*享的函数。 - 不能做到完全屏蔽 iOS 端或 Android 的细节,前端开发者必须对原生平台有所了解。加重了学习成本。对于移动端开发者来说,完全不具备用 React Native 开发的能力。
- 由于 Objective-C 与 JavaScript 之间切换存在固定的时间开销,所以性能必定不及原生。比如目前的官方版本无法做到 UItableview(ListView) 的视图重用,因为滑动过程中,视图重用需要在异步线程中执行,速度太慢。这也就导致随着 Cell 数量的增加,占用的内存也线性增加。
综上,我对 React Native 的定位是:
利用脚本语言进行原生平台开发的一次成功尝试,降低了前端开发者入门移动端的门槛,一定业务场景下具有独特的优势,几乎不可能取代原生平台开发。
其他补充
二、常见的五种App开发模式
- 常见的开发模式有5种(Native App,Web App,Hybrid App,Weex,React Native)
Native App
- Native App:指使用原生API开发App,比如iOS用OC语言开发
- 优点:性能高
- 缺点:开发维护成本高,养一个原生开发工程师需要很多钱,最重要iOS版本更新也成问题。
Web App
- Web App:指使用Html开发的移动端网页App,类似微信小程序,整个App都是网页。
- 优点:用户不需要安装,不会占用手机内存
- 缺点:用户体验不好,不能离线,必须联网
Hybrid App
- HybridApp:混合开发模式,原生Api+Html共同开发,比如iOS,用html写好界面,用UIWebView展示。
- 优点:界面复用性强,一个界面,iOS和安卓都可以使用
- 缺点:相对于原生,性能相对有所损害
Weex
- Weex:基于Vue(JS框架)的语法开发的App,底层会自动把JS代码解析成对应平台(iOS,安卓)的原生API,本质还是原生API开发,只不过表面是用Vue开发。
- 优点:可以做到一套代码,跨平台执行,底层会自动判断当前是哪个平台,转换为对应平台的原生API代码。
- 缺点:开源较晚,互联网上相关资料还比较少,社区规模较小
React Native
- React Native:基于React开发的App
- 优点:跳过App Store审核,远程更新代码,提高迭代频率和效率,既有Native的体验,又保留React的开发效率。
- 缺点:对于不熟悉前端开发的人员上手比较慢,不能真正意义上做到跨平台,使用后,对app体积增加。
- 相信大多数人了解完React Native,越来越困惑了,那不是跟Native冲突了吗,Native是用原生Api开发,但是React Native又是用React开发。
- 要想彻底搞明白,需要了解React Native底层实现原理,又来了,想知道原理,继续往下看
三、React Native和Weex区别
- React Native原理其实跟Weex差不多,底层也会把React转换为原生API
- React Native和Weex区别在于跨平台上面,Weex只要写一套代码,React Native需要iOS,安卓都写,说明React Native底层解析原生API是分开实现的,iOS一套,安卓一套。
四、React Native如何把React转化为原生API
- React Native会在一开始生成OC模块表,然后把这个模块表传入JS中,JS参照模块表,就能间接调用OC的代码。
- 相当于买了一个机器人(OC),对应一份说明书(模块表),用户(JS)参照说明书去执行机器人的操作。
五、React Native是如何做到JS和OC交互
- iOS原生API有个JavaScriptCore框架,通过它就能实现JS和OC交互,想了解JavaScriptCore,请点击JavaScriptCore
- 1.首先写好JSX代码(React框架就是使用JSX语法)
- 2.把JSX代码解析成javaScript代码
- 3.OC读取JS文件
- 4.把javaScript代码读取出来,利用JavaScriptCore执行 (oc 定时调用方法询问js 是否有需要执行的原生方法 )
- 5.javaScript代码返回一个数组,数组中会描述OC对象,OC对象的属性,OC对象所需要执行的方法,这样就能让这个对象设置属性,并且调用方法。
JS和OC交互.png
六、React Native启动流程(iOS)
- 1.创建RCTRootView -> 设置窗口根控制器的View,把RN的View添加到窗口上显示。
- 2.创建RCTBridge -> 桥接对象,管理JS和OC交互,做中转左右。
- 3.创建RCTBatchedBridge -> 批量桥架对象,JS和OC交互具体实现都在这个类中。
- 4.执行[RCTBatchedBridge loadSource] -> 加载JS源码
- 5.执行[RCTBatchedBridge initModulesWithDispatchGroup] -> 创建OC模块表
- 6.执行[RCTJSCExecutor injectJSONText] -> 往JS中插入OC模块表
- 7.执行完JS代码,回调OC,调用OC中的组件
- 8.完成UI渲染
React Native启动流程(iOS).png
七、React Native加载JS源码流程(iOS)
- 1.[RCTJavaScriptLoader loadBundleAtURL] -> 加载远程服务器中JS代码
- 2.attemptAsynchronousLoadOfBundleAtURL(C函数) -> 开启异步加载JS代码
- 3.[RCTBatchedBridge executeSourceCode:sourceCode] -> 让批量交接对象执行源代码
- [RCTJSCExecutor executeApplicationScript] -> 交给JS执行者(RCTJSCExecutor)执行源码)
- 真正执行JS代码的是
RCTJSCExecutor
对象
- 5.[postNotificationName:RCTJavaScriptDidLoadNotification] -> 发送JS代码执行完成通知
- 6.RCTRootView监听到RCTJavaScriptDidLoadNotification通知
- 7.创建RCTRootContentView
- 8.获取RCTBridge中的RCTUIManager注册RCTRootView,并且记录RCTRootView,_viewRegistry
-
RCTUIManager
:管理UI组件 - _viewRegistry:保存所有注册的View
-
- 9.[RCTRootView runApplication:bridge] -> 通知JS运行App
- 10.[RCTBridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL] -> 通过桥接对象让JS调用AppRegistry - 11.[RCTBatchedBridge _actuallyInvokeAndProcessModule:module method:method arguments:args queue:RCTJSThread] -> 通过批量桥架让JS执行AppRegistry方法
- 12.[RCTJSCExecutor _executeJSCall:bridgeMethod arguments:@[module, method, args] unwrapResult:unwrapResult callback:onComplete] -> 让JS执行者调用JS代码
- 13.执行完JS代码,就能获取执行JS结果,是一个数组,OC需要做的事情都会保存到这个数组中
- 14.[RCTBatchedBridge _processResponse:json error:error] -> 处理执行完JS代码返回的结果对象
- 15.[RCTBatchedBridge handleBuffer] -> 处理JS返回的数据,JS会返回的方法调用数组:按顺序描述需要调用哪个对象的方法,一组调用包含(module,method,arguments)
- 16.[self callNativeModule:[moduleIDs[index] integerValue]
method:[methodIDs[index] integerValue]
params:paramsArrays[index]] -> 遍历数组,依次执行原生方法- 注意:当前方法,在遍历数组中的代码块中执行,不只是执行一次.
React Native加载JS源码流程.png
八、React NativeUI控件渲染流程(iOS)
- 1.[RCTRootView runApplication:bridge] -> 通知JS运行App
- 2.[RCTBatchedBridge _processResponse:json error:error] -> 处理执行完JS代码(runApplication)返回的相应,包含需要添加多少子控件的信息。
- 3.[RCTBatchedBridge batchDidComplete] -> 批量桥架对象调用批量处理完成方法
- 4.[RCTUIManager batchDidComplete] -> RCTUIManager调用批量处理完成的方法,就会开始去加载rootView的子控件。
-
5.[RCTUIManager createView:viewName:rootTag:props] -> 通过JS执行OC代码,让UI管理者创建子控件View
- 通过
RCT_EXPORT_METHOD宏
定义createView这个方法
RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag
viewName:(NSString *)viewName
rootTag:(__unused NSNumber *)rootTag
props:(NSDictionary *)props)-
RCT_EXPORT_METHOD宏
:会在JS中生成对应的OC方法,这样JS就能直接调用- 注意每创建一个UIView,就会创建一个RCTShadowView,与UIView一一对应
-
RCTShadowView
:保存对应UIView的布局和子控件,管理UIView的加载
- 通过
- 6.[RCTUIManager _layoutAndMount] -> 布局RCTRootView和增加子控件
- 7.[RCTUIManager setChildren:reactTags:] -> 给RCTRootView对应的RCTRootShadowView设置子控件
- 注意:此方法也是JS调用OC方法
- 8.[RCTRootShadowView insertReactSubview:view atIndex:index++] -> 遍历子控件数组,给RCTRootShadowView插入所有子控件
- 9.[RCTShadowView processUpdatedProperties:parentProperties:] -> 处理保存在RCTShadowView中属性,就会去布局RCTShadowView对应UIView的所有子控件
- 10.[RCTView didUpdateReactSubviews] -> 给原生View添加子控件
- 11.完成UI渲染
React Native (UI控件渲染流程).png
九、React Native事件处理流程(iOS)
- 1.在创建RCTRootContentView的时候,内部会创建RCTTouchHandler
-
RCTTouchHandler
:继承UIGestureRecognizer,也就是它就是一个手势- 并且它会作为RCTRootContentView的手势,这样点击RCTRootContentView,就会触发RCTTouchHandler
-
RCTTouchHandler
:内部实现了touchBegin等触摸方法,用来处理触摸事件
-
- 2.在创建RCTTouchHandler的时候,内部会创建RCTEventDispatcher
-
RCTEventDispatcher
:用来把事件处理传递给JS的方法处理,也就是当UI界面产生事件,就会执行JS的代码处理。
-
- 3.通过RCTRootContentView截获点击事件
- 产生事件就会去触犯RCTRootContentView中的RCTTouchHandler对象。
- 4.当产生事件的时候,会执行[RCTTouchHandler touchBegin]
- 5.RCTTouchHandler的touch方法,会执行[RCTTouchHandler _updateAndDispatchTouches:eventName:]
- 内部会创建RCTTouchEvent事件对象
- 6.[RCTEventDispatcher sendEvent:event] -> 让事件分发对象调用发送事件对象
- 内部会把事件保存到_eventQueue(事件队列中)
- 7.[RCTEventDispatcher flushEventsQueue] -> 让事件分发对象冲刷事件队列,就是获取事件队列中所有事件执行
- 8.[RCTEventDispatcher dispatchEvent:event] -> 遍历事件队列,一个一个分发事件
- 分发事件的本质:就是去执行JS的代码,相应事件。
- 9.[RCTBatchedBridge enqueueJSCall:[[event class] moduleDotMethod] args:[event arguments]]; -> 让桥架对象调用JS处理事件
- 本质:就是产生事件调用JS代码
- 10.这样就能完成把UI事件交给JS代码相应
React Native (事件处理流程).png
接下来会讲解一些RN的环境配置及解决运行错误 敬请期待