Bridge
RCTRootView
RCTRootView是React Native加载的地方,是万物之源。从这里开始,我们有了JS Engine, JS代码被加载进来,对应的原生模块也被加载进来,然后js loop开始运行。js loop的驱动来源是Timer和Event Loop(用户事件). js loop跑起来以后应用就可以持续不停地跑下去了。
如果你要通过调试来理解RN底层原理,你也应该是从RCTRootView着手,顺藤摸瓜。
每个项目的AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:里面都可以看到RCTRootView的初始化代码,RCTRootView初始化完成以后,整个React Native运行环境就已经初始化好了,JS代码也加载完毕,所有React的绘制都会有这个RCTRootView来管理。
RCTRootView做的事情如下:
创建并且持有RCTBridge
加载JS Bundle并且初始化JS运行环境.
初始化JS运行环境的时候在App里面显示loadingView,注意不是屏幕顶部的那个下拉悬浮进度提示条. RN第一次加载之后每次启动非常快,很少能意识到这个加载过程了。loadingView默认情况下为空,也就是默认是没有效果的。loadingView可以被自定义,直接覆盖RCTRootView.loadingView就可以了.开发模式下RN app第一次启动因为需要完整打包整个js所以可以很明显看到加载的过程,加载第一次以后就看不到很明显的加载过程了,可以执行下面的命令来触发重新打包整个js来观察loadingView的效果watchman watch-del-all && rm -rf node_modules/ && yarn install && yarn start – –reset-cache
,然后杀掉app重启你就会看到一个很明显的进度提示.
JS运行环境准备好以后把加载视图用RCTRootContentView替换加载视图.
所有准备工作就绪以后调用AppRegistry.runApplication正式启动RN JS代码,从Root Component()开始UI绘制。
一个App可以有多个RCTRootView,初始化的时候需要手动传输Bridge做为参数,全局可以有多个RCTRootView,但是只能有一个Bridge.
如果你做过React Native和原生代码混编,你会发现混编就是把AppDelegate里面那段初始化RCTRootView的代码移动到需要混编的地方,然后把RCTRootView做为一个普通的subview来加载到原生的view里面去,非常简单。不过这地方也要注意处理好单Bridge实例的问题,同时,混编里面要注意RCTRootView如果销毁过早可能会引发JS回调奔溃的问题。
RCTRootContentView
RCTRootContentView reactTag在默认情况下为1.在Xcode view Hierarchy debugger下可以看到,最顶层为RCTRootView,里面嵌套的是RCTRootContentView,从RCTRootContentView开始,每个View都有一个reactTag.
RCTRootView继承自UIView, RCTRootView主要负责初始化JS Environment和React代码,然后管理整个运行环境的生命周期。RCTRootContentView继承自RCTView, RCTView继承自UIView, RCTView封装了React Component Node更新和渲染的逻辑,RCTRootContentView会管理所有react ui components. RCTRootContentView同时负责处理所有touch事件.
RCTBridge
这是一个加载和初始化专用类,用于前期JS的初始化和原生代码的加载。
负责加载各个Bridge模块供JS调用
找到并注册所有实现了RCTBridgeModule protocol的类,供JS后期使用.
创建和持有RCTBatchedBridge
RCTBatchedBridge
如果RCTBridge是总裁,那么RCTBatchedBridge就是副总裁。前者负责发号施令,后者负责实施落地。
负责Native和JS之间的相互调用(消息通信)
持有JSExecutor
实例化所有在RCTBridge里面注册了的native node_modules
创建JS运行环境,注入native hooks和modules,执行JS bundle script
管理JS run loop,批量把所有JS到native的调用翻译成native invocations
批量管理原生代码到JS的调用,把这些调用翻译成JS消息发送给JS executor
RCTJavaScriptLoader
这是实现远程代码加载的核心。热更新,开发环境代码加载,静态jsbundle加载都离不开这个工具。
从指定的地方(bundle, http server)加载script bundle
把加载完成的脚本用string的形式返回
处理所有获取代码、打包代码时遇到的错误
RCTContextExecutor
封装了基础的JS和原生代码互掉和管理逻辑,是JS引擎切换的基础。通过不同的RCTCOntextExecutor来适配不同的JS Engine,让我们的React JS可以在iOS、Android、chrome甚至是自定义的js engine里面执行。这也是为何我们能在chrome里面直接调试js代码的原因。
管理和执行所有N2J调用
RCTModuleData
加载和管理所有和JS有交互的原生代码。把需要和JS交互的代码按照一定的规则自动封装成JS模块。
收集所有桥接模块的信息,供注入到JS运行环境
RCTModuleMethod
记录所有原生代码的导出函数地址(JS里面是不能直接持有原生对象的),同时生成对应的字符串映射到该函数地址。JS调用原生函数的时候会通过message的形式调用过来。
记录所有的原生代码的函数地址,并且生成对应的字符串映射到该地址
记录所有的block的地址并且映射到唯一的一个id
翻译所有J2N call,然后执行对应的native方法。
如果是原生方法的调用则直接通过方法名调用,MessageQueue会帮忙把Method翻译成MethodID,然后转发消息给原生代码,传递函数签名和参数给原生MessageQueue,最终给RCTModuleMethod解析调用最终的方法
如果JS调用的是一个回调block,MessageQueue会把回调对象转化成一个一次性的block id,然后传递给RCTModuleMethod,最终由RCTModuleMethod解析调用。基本上和方法调用一样,只不过生命周期会不一样,block是动态生成的,要及时销毁,要不然会导致内存泄漏。
注:
实际上是不存在原生MessageQueue对象模块的,JS的MessageQueue对应到原生层就是RCTModuleData & RCTModuleMethod的组合, MessageQueue的到原生层的调用先经过RCTModuleData和RCTModuleMethod翻译成原生代码调用,然后执行.