迁移老文章到掘金(基于RN 0.26)html
相关系列文章前端
本篇前两部份内容简单介绍一下ReactNative,后面的章节会把整个RN框架的iOS部分,进行代码层面的一一梳理java
全文是否是有点太长了,我要不要分拆成几篇文章react
函数栈代码流程图,因为采用层次缩进的形式,层次关系比较深的话,不是很利于手机阅读,git
ReactNative,动态,跨平台,热更新,这几个词如今愈来愈火了,一句使用JavaScript写源生App
吸引力了无数人的眼球,而且诞生了这么久也逐渐趋于稳定,携程
,天猫
,QZone
也都在大产品线的业务上,部分模块采用这个方案上线,而且效果获得了验证(见2016 GMTC 资料PPT)github
咱们把这个单词拆解成2部分算法
熟悉前端的朋友们可能都知道React.JS
这个前端框架,没错整个RN框架的JS代码部分,就是React.JS,全部这个框架的特色,完彻底全均可以在RN里面使用(这里还融入了Flux,很好的把传统的MVC重组为dispatch,store和components,Flux架构)数据库
因此说,写RN哪不懂了,去翻React.JS的文档或许都能给你解答数组
以上由@彩虹 帮忙修正浏览器
顾名思义,纯源生的native体验,纯源生的UI组件,纯原生的触摸响应,纯源生的模块功能
那么这两个不相干的东西是如何关联在一块儿的呢?
React.JS是一个前端框架,在浏览器内H5开发上被普遍使用,他在渲染render()这个环节,在通过各类flexbox布局算法以后,要在肯定的位置去绘制这个界面元素的时候,须要经过浏览器去实现。他在响应触摸touchEvent()这个环节,依然是须要浏览器去捕获用户的触摸行为,而后回调React.JS
上面提到的都是纯网页,纯H5,但若是咱们把render()这个事情拦截下来,不走浏览器,而是走native会怎样呢?
当React.JS已经计算完每一个页面元素的位置大小,原本要传给浏览器,让浏览器进行渲染,这时候咱们不传给浏览器了,而是经过一个JS/OC的桥梁,去经过[[UIView alloc]initWithFrame:frame]
的OC代码,把这个界面元素渲染了,那咱们就至关于用React.JS绘制出了一个native的View
拿咱们刚刚绘制出得native的View,当他发生native源生的- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
触摸事件的时候,经过一个OC/JS的桥梁,去调用React.JS里面写好的点击事件JS代码
这样React.JS仍是那个React.JS,他的使用方法没发生变化,可是却得到了纯源生native的体验,native的组件渲染,native的触摸响应
因而,这个东西就叫作React-Native
你们能够看到,刚才我说的核心就是一个桥梁,不管是JS=>OC,仍是OC=>JS。
刚才举得例子,就至关于把纯源生的UI模块,接入这个桥梁,从而让源生UI与React.JS融为一体。
那咱们把野心放长远点,咱们不止想让React.JS操做UI,我还想用JS操做数据库!不管是新玩意Realm,仍是老玩意CoreData,FMDB,我都但愿能用JS操做应该怎么办?好办,把纯源生的DB代码模块,接入这个桥梁
若是我想让JS操做Socket作长链接呢?好办,把源生socket代码模块接入这个桥梁。若是我想让JS能操做支付宝,微信,苹果IAP呢?好办,把源生支付代码模块接入这个桥梁
因而可知RN就是由一个bridge桥梁,链接起了JS与na的代码模块
这是一个极度模块化可扩展的桥梁框架,不是说你从facebook的源上拉下来RN的代码,RN的能力就固定一成不变了,他的模块化可扩展,让你缺啥补上啥就行了
ReactNative 结构图
你们能够看这个结构图,整个RN的结构分为四个部分,上面提到的,RN桥的模块化可扩展性,就体如今JSBridge/OCBridge里的ModuleConfig,只要遵循RN的协议RCTBridgeModule
去写的OC Module对象,使用RCT_EXPORT_MODULE()
宏注册类,使用RCT_EXPORT_METHOD()
宏注册方法,那么这个OC Module以及他的OC Method都会被JS与OC的ModuleConfig进行统一控制
上面是RN的代码类结构图
你们能够看到RCTRootView
是RN的根试图,
他内部持有了一个RCTBridge
,可是这个RCTBridge并无太多的代码,而是持有了另外一个RCTBatchBridge
对象,大部分的业务逻辑都转发给BatchBridge,BatchBridge里面写着的大量的核心代码
BatchBridge会经过RCTJavaScriptLoader
来加载JSBundle,在加载完毕后,这个loader也没什么太大的用了
BatchBridge会持有一个RCTDisplayLink
,这个对象主要用于一些Timer,Navigator的Module须要按着屏幕渲染频率回调JS用的,只是给部分Module需求使用
RCTModuleXX
全部的RN的Module组件都是RCTModuleData,不管是RN的核心系统组件,仍是扩展的UI组件,API组件
RCTJSExecutor
是一个很特殊的RCTModuleData,虽然他被当作组件module一块儿管理,统一注册,但他是系统组件的核心之一,他负责单独开一个线程,执行JS代码,处理JS回调,是bridge的核心通道RCTEventDispatcher
也是一个很特殊的RCTModuleData,虽然他被当作组件module一块儿管理,统一注册,可是他负责的是各个业务模块经过他主动发起调用js,好比UIModule,发生了点击事件,是经过他主动回调JS的,他回调JS也是经过RCTJSExecutor
来操做,他的做用是封装了eventDispatcher得API来方便业务Module使用。后面我会详细按着代码执行的流程给你们细化OCCode里面的代码,JSCode因为我对前端理解还不太深刻,这个Blog就不会去拆解分析JS代码了
ReactNative通讯机制能够参考bang哥的博客 React Native通讯机制详解
我会按着函数调用栈相似的形式梳理出一个代码流程表,对每个调用环节进行简单标记与做用说明,在整个表梳理完毕后,我会一一把每一个标记进行详细的源码分析和解释
下面的代码流程表,若是有类名+方法的,你能够直接在RN源码中定位到具体代码段
RootInit标记:全部RN都是经过init方法建立的再也不赘述,URL能够是网络url,也能够是本地filepath转成URL
BatchBridgeInit标记:前边说过rootview会先持有一个RCTBridge,全部的module都是直接操做bridge所提供的接口,可是这个bridge基本上不干什么核心逻辑代码,他内部持有了一个batchbrdige,各类调用都是直接转发给RCTBatchBrdige来操做,所以batchbridge才是核心
RCTBridge在init的时候调用[self setUp]
RCTBridge在setUp的时候调用[self createBatchedBridge]
DisplaylinkInit标记:batchbridge会首先初始化一个RCTDisplayLink
这个东西在业务逻辑上不会被全部的module调用,他的做用是以设备屏幕渲染的频率触发一个timer,判断是否有个别module须要按着timer去回调js,若是没有module,这个模块其实就是空跑一个displaylink,注意,此时只是初始化,并无run这个displaylink
dispatchQueueInit标记:会初始化一个GCDqueue,后面不少操做都会被扔到这个队列里,以保证顺序执行
dispatchGroupInit标记:后面接下来进行的一些列操做,都会被添加到这个GCDgroup之中,那些被我作了group Enter标记的,当group内全部事情作完以后,会触发group Notify
__groupEnterLoadSource__标记:会把不管是从网络仍是从本地,拉取jsbundle这个操做,放进GCDgroup之中,这样只有这个操做进行完了(还有其余group内操做执行完了,才会执行notify的任务)
loadJS标记:其实就是异步去拉取jsbundle,不管是本地读仍是网络啦,[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onComplete:onSourceLoad];
只有当回调完成以后会执行dispatch_group_leave
,离开group
InitModule标记:这个函数是在主线程被执行的,可是刚才生成的GCD group会被当作参数传进内部,由于内部的一些逻辑是须要加入group的,这个函数内部很复杂 我会继续绘制一个代码流程表
一个C函数,RCT_EXPORT_MODULE()注册宏会在+load
时候把Module类都统一管理在一个static NSArray里,经过RCTGetModuleClasses()能够取出来全部的Module
此处是一个for循环,循环刚才拿到的array,对每个注册了得module都循环生成RCTModuleData实例
每个module在循环生成结束后,bridge会统一存储3分配置表,包含了全部的moduleConfig的信息,便于查找和管理
//barchbridge的ivar
NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
NSArray<RCTModuleData *> *_moduleDataByID;
NSArray<Class> *_moduleClassesByID;
// Store modules
_moduleDataByID = [moduleDataByID copy];
_moduleDataByName = [moduleDataByName copy];
_moduleClassesByID = [moduleClassesByID copy];
复制代码
这是一个for循环,每个RCTModuleData都须要循环instance一下,须要说明的是,RCTModuleData与Module不是一个东西,各种Module继承自NSObject,RCTModuleData内部持有的instance实例才是各种Module,所以这个环节是初始化RCTModuleData真正各种Module实例的环节
经过RCTModuleData-setUpInstanceAndBridge
来初始化建立真正的Module
//SOME CODE
_instance = [_moduleClass new];
//SOME CODE
[self setUpMethodQueue];
复制代码
这里须要说明,每个Module都会建立一个本身独有的专属的串行GCD queue,每次js抛出来的各个module的通讯,都是dispatch_async,不必定从哪一个线程抛出来,但能够保证每一个module内的通讯事件是串行顺序的
每个module都有个bridge属性指向,rootview的bridge,方便快速调用
RCTJSCExecutor是一个特殊的module,是核心,因此这里会单独处理,生成,初始化,而且被bridge持有,方便直接调用
RCTJSCExecutor初始化
作了不少事情,须要你们仔细关注一下
建立了一个全新的NSThread,而且被持有住,绑定了一个runloop,保证这个线程不会消失,一直在loop,全部与JS的通讯,必定都经过RCTJSCExecutor来进行,因此必定是在这个NSThread线程内,只不过各个模块的消息,会进行二次分发,不必定在此线程内
每个module都有本身的提供给js的接口配置表,这个方法就是读取这个配置表,注意!这行代码执行在主线程,但他使用dispatch_async 到mainQueue上,说明他先放过了以前的函数调用栈,等以前的函数调用栈走完,而后仍是在主线程执行这个循环的gatherConstants,所以以前传进来的GCD group派上了用场,由于只有当全部module配置都读取并配置完毕后才能够进行 run js代码
下面思路从子代码流程表跳出,回到大代码流程表的标记
groupEnterJSConfig标记:代码到了这块会用到刚才建立,但一直没使用的GCD queue,而且这块还比较复杂,在此次enter group内部,又建立了一个子group,都放在这个GCD queue里执行
若是以为绕能够这么理解他会在专属的队列里执行2件事情(后面要说的2各标记),当这2个事情执行完后触发子group notify,执行第三件事情(后面要说的第三个标记),当第三个事情执行完后leave母group,触发母group notify
dispatch_group_enter(initModulesAndLoadSource);
dispatch_async(bridgeQueue, ^{
dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();
// Asynchronously initialize the JS executor
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
RCTPerformanceLoggerStart(RCTPLJSCExecutorSetup);
[weakSelf setUpExecutor];
RCTPerformanceLoggerEnd(RCTPLJSCExecutorSetup);
});
// Asynchronously gather the module config
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
if (weakSelf.valid) {
RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig);
config = [weakSelf moduleConfig];
RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig);
}
});
dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
// We're not waiting for this to complete to leave dispatch group, since
// injectJSONConfiguration and executeSourceCode will schedule operations
// on the same queue anyway.
RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig);
[weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig);
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
}];
dispatch_group_leave(initModulesAndLoadSource);
});
});
复制代码
configJSExecutor标记:再次专门处理一些JSExecutor这个RCTModuleData
1)property context懒加载,建立了一个JSContext
2)为JSContext设置了一大堆基础block回调,都是一些RN底层的回调方法
moduleConfig标记:把刚才全部配置moduleConfig信息汇总成一个string,包括moduleID,moduleName,moduleExport接口等等
moduleConfigInject标记:把刚才的moduleConfig配置信息string,经过RCTJSExecutor,在他内部的专属Thread内,注入到JS环境JSContext里,完成了配置表传给JS环境的工做
groupDone标记:GCD group内全部的工做都已完成,loadjs完毕,配置module完毕,配置JSExecutor完毕,能够放心的执行JS代码了
evaluateJS标记:经过[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:]
来在JSExecutor专属的Thread内执行jsbundle代码
addrunloop标记:最先建立的RCTDisplayLink
一直都只是建立完毕,但并无运做,此时把这个displaylink绑在JSExecutor的Thread所在的runloop上,这样displaylink开始运做
小结:
整个RN在bridge上面,单说OC侧,各类GCD,线程,队列,displaylink,仍是挺复杂的,针对各个module也都是有不一样的处理,把这块梳理清楚能让咱们更加清楚OC代码里面,RN的线程控制,更方便之后咱们扩展编写更复杂的module模块,处理更多native的线程工做。
后面的 js call oc oc call js 我也会以一样的方式进行梳理,让你们清楚线程上是如何运做的
PS:JS代码侧其实bridge的设计也有一套,包括全部call oc messageQueue会有个队列控制之类的,我对JS不是那么熟悉和理解,JS侧的代码我就不梳理了。
既然整个RCTRootView都初始化完毕,而且执行了jsbundle文件了,整个RN就已经运做起来了,那么RN运做起来后,JS的消息经过JS代码的bridge发送出来以后,是如何被OC代码识别,分发,最重转向各个module模块的业务代码呢?咱们接下来就会梳理,这个流程的代码
JS call OC 能够有不少个方法,可是全部的方法必定会走到同一个函数内,这个关键函数就是
- (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded
须要说明的事,handleBuffer必定发生在RCTJSExecutor的Thread内
正所谓顺藤摸瓜,我能够顺着他往上摸看看都哪里会发起js2oc的通讯
能够看到这里面有不少JavaScriptCore的JSContext["xxx"]=block的用法,这个用法就是JS能够把xxx当作js里面能够识别的function,object,来直接调用,从而调用到block得意思,能够看出来nativeFlushQueueImmediate
当js主动调用这个jsfunction的时候,就会下发一下数据,从而调用handleBuffer,能够肯定的是,这个jsfunction,会在jsbunlde run起来后马上执行一次
这个方法要特别强调一下,这是惟一个一个JS会主动调用OC的方法,其余的js调用OC,都他由OC实现传给JS一个回调,让JS调用。
JS侧主动调用nativeFlushQueueImmediate的逻辑
能够看到这句代码只发生在执行jsbundle以后,执行以后会[RCTJSExecutor flushedQueue:callback]
在callback里调用handleBuffer,说明刚刚执行完jsbundle后会由OC主动发起一次flushedQueue,而且传给js一个回调,js经过这个回调,会call oc,进入handleBuffer
两个_actuallyInvoke开头的方法,用处都是OC主动发起调用js的时候,会传入一个call back block,js经过这个callback block回调,这两个方法最后都会执行[RCTJSExecutor _executeJSCall:]
从上面能够看出JS只有一个主动调用OC的方法,其余都是经过OC主动调用JS给予的回调
咱们还能够顺着handleBuffer往下摸看看都会如何分发JS call OC的事件
以handleBuffer为根,咱们继续用函数站代码流程表来梳理
analyze buffer标记:js传过来的buffer实际上是一串calls的数组,一次性发过来好几个消息,须要OC处理,因此会解析buffer,分别识别出每个call的module信息
NSArray<NSNumber *> *moduleIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldRequestModuleIDs]];
NSArray<NSNumber *> *methodIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldMethodIDs]];
NSArray<NSArray *> *paramsArrays = [RCTConvert NSArrayArray:requestsArray[RCTBridgeFieldParams]];
复制代码
find modules标记:解析了buffer以后就要查找对应的module,不只要找到RCTModuleData,同时还要取出RCTModuleData本身专属的串行GCD queue
dispatch async标记:每个module和queue都找到了就能够for循环了,去执行一段代码,尤为要注意,此处RN的处理是直接dispatch_async到系统随机某一个空闲线程,由于有模块专属queue的控制,仍是能够保持不一样模块内消息顺序的可控
invocation标记:这个标记的做用就是真真正正的去调用而且执行对应module模块的native代码了,也就是JS最终调用了OC,这个标记内部还比较复杂,里面使用了NSInvocation去运行时查找module类进行反射调用
invocation内部子流程以下
解释一下,JS传给OC是能够把JS的回调当作参数一并传过来的,因此后面的流程中会特别梳理一下这种回调参数是如何实现的,
invocation预处理标记:RN会提早把即将反射调用的selector进行分析,分析有几个参数每一个参数都是什么类型,每种类型是否须要包装或者转化处理。
参数处理标记:argumentBlocks实际上是包装或转化处理的block函数,每种参数都有本身专属的block,根据类型进行不一样的包装转化策略
此处别的参数处理不细说了,单说一下JS回调的这种参数是怎么操做的
[RCTBridge enqueueCallback:]
在须要的时候回调JS,而后把这个block压入参数,等待传给module这块代码各类宏嵌套,还真是挺绕的,由于宏的形式,可读性很是之差,可是读完了后仍是会以为很风骚
[RCTBridgeMethod processMethodSignature]
这个方法,强烈推荐
invocation压参标记:argumentBlocks能够理解为预处理专门准备的处理每一个参数的函数,那么预处理结束后,就该循环调用argumentBlocks把每个参数处理一下,而后压入invocation了
后面就会直接调用到你写的业务模块的代码了,业务模块经过那个callback回调也能直接calljs了
咱们编写module,纯源生native模块的时候,有时候会有主动要call js的需求,而不是经过js给的callback calljs
这时候就须要RCTEventDispatcher
了,能够看到他的头文件里都是各类sendEvent,sendXXXX的封装,看一下具体实现就会发现,不管是怎么封装,最后都走到了[RCTJSExecutor enqueueJSCall:]
,追中仍是经过RCTJSExecutor,主动发起调用了JS
他有两种方式
以前咱们提到过一个RCTDisplayLink
,没错他被添加到RCTJSExecutor的Thread所在的runloop之上,以渲染频率触发执行代码,执行frameupDate
[RCTDisplaylink _jsThreadUpdate]
在这个方法里,会拉取全部的须要执行frameUpdate的module,在module所在的队列里面dispatch_async执行didUpdateFrame方法
在各自模块的didUpdateFrame方法内,会有本身的业务逻辑,以DisplayLink的频率,主动call js
好比:RCTTimer模块
最后在强调下JSBridge这个管道的线程控制的状况
刚才提到的不管是OC Call JS仍是JS call OC,都只是在梳理代码流程,让你清楚,全部JS/OC之间的通讯,都是经过RCTJSExecutor,都是在RCTJSExecutor内部所在的Thread里面进行
若是发起调用方OC,并非在JSThread执行,RCTJSExecutor就会把代码perform到JSThread去执行
发起调用方是JS的话,全部JS都是在JSThread执行,因此handleBuffer也是在JSThread执行,只是在最终分发给各个module的时候,才进行了async+queue的控制分发。