RN iOS端启动过程解析

RN 版本: 0.59.10

1. 创建RCTBridge

RCTBridge内部持有RCTCxxBridge实例,这是实际的执行者,继承自RCTBridge。RCTCxxBridge通过弱引用parentBridge访问他的持有者

图1-RCTBridge初始化

2. 注册module

创建RCTBridge之后,就是启动RCTCxxBridge. 启动过程中先创建js 运行线程,这个线程跟runloop绑定,会一直运行下去, 然后开始注册module。


图2-RCTCxxBridge启动

这里有两种module, 第一种是module是通过外部传进来或者是通过代理得到的,在Debug模式下会检查其类名和是否遵循了RCTBridgeModule协议,里面根据是否是懒加载又可以分为两种,加载顺序有所不同(一个最前,一个最后) 。第二种module是我们通过RCT_EXTERN宏包装的module(),这些module类会在程序启动时通过load方法内调用 RCTRegisterModule方法把自己添加到module classes数组里面,当RCTCxxBridge时从module classes 里面读出来,RCTCxxBridge对这些module做的工作主要是做一些例行的检查工作以及收集其暴露的常量。

图3-RCT_EXTERN宏定义
图4-注册module的方法

这些module的信息以moduleData或者class的形式存放在modueDataByname , moduleDataById和moduleClassesById 三个容器中。RN的模块都是以ID或者name来区分的,不管是加载还是调用方法都需要用到这三个容器。


图5-moduleData的保存

3. 创建和重置executor

注册module之后,RCTCxxBridge会创建一个executorFactory,用于生成执行JS方法的类。

这里用到了抽象工厂模式,JSExecutorFactory是抽象工厂类, RCTObjcExecutorFactory和JSCExecutorFactory是具体工厂类。 RCTObjcExecutorFactory通过传进来的jse 参数(id类型)来确定具体的JS执行者,有可能是RCTWebSocketExecutor或者其他类型的executor。 而JSCExecutorFactory则指定了JS执行者为JSCRuntime,即iOS 自带的JavascriptCore。

默认情况下(第一次启动APP且没有开启过debug模式),我们会使用JSCExecutorFactory(JavascriptCore),但如果开启了debug,则会走websocket 发送指定给远端的执行者。如果开发者自己实现了RCTCxxBridgeDelegate接口并赋值给了delegate,就可以使用自定义的JS引擎。

图6 重置executorFactory

4.初始化Bridge

图7 创建executorFactory及初始化bridge

这里先创建了一个jsMessageThread 用于跟js通讯,注意这个handleError方法,当我们JS端代码执行出错的时候,就会走到这里来,显示红屏警告,所以如果想知道具体哪里出错,可以在这里打断点看看调用的堆栈

图8 创建js 线程

然后 调用createNativeModules方法把前面moduleClasses里面的moduleData 转换成对应的RCTCxxModule,并且把moduleDataById容器传给moduleRegistry 。后面moduleRegistry 作为js调用原生方法的一个中介,直接用这个容器查找对应module并调用相关方法。

图9 createNativeModule

其中moduleClass里面暴露的方法会根据其类型是异步、同步、还是普通的来分别放到对应的methodNames里面,并包装成RCTCxxModule的一部分, 返回给调用方。

图10 获取moduleClass暴露的方法
图11 判断方法类型

RCTCxxBridge再把这些信息组装成moduleRegistry,并交给ReactInstance保存起来,后面原生向js 发消息就主要用到这些信息。 需要注意,这个初始化bridge的过程是在前面创建的js运行线程执行的,以确保不会影响主线程。

ReactInstance用这些信息创建了nativeToJSBridge, 后续原生向js发送消息就是用的这个对象。

图12 组装信息并交给reactInstance
图 13 创建nativeToJSBridge

NativeToJSBridge初始化的时候顺便把JSToNativeBridge也初始化了,通过代理的形式来调用,注意这个代理是由executorFactory创建的JSExecutor持有的。

图13-2 JSToNativeBridge

5.加载rn代码

这一步跟初始化Bridge是同时进行的,由一个dispatch_group进行管理。这一步会调用RCTJavaScriptLoader加载bundle。这里会先尝试同步从本地读取bundle,如果不存在或者不能同步读取,则尝试异步读取。异步读取也是先从本地读,读不到则去远程服务器上获取。这里需要注意的是开启了Debug模式后,远程服务器就是我们电脑上的npm, 我们的JS代码实际是运行在Chrome上的V8引擎内。npm作为一个中间服务,主要提供源码的下载和通信,也就是与客户端和chrome分别建立连接,并转发指令。原生这边先通过一个HTTP请求下载源码到本地, 然后由RCTWebSocketExecutor来进行指令的发送和接收,这个RCTWebSocketExecutor会作为RCTBridge的一个属性(executorClass)保存下来,在切换Debug模式时会改变executorClass, 然后在reload 时重置executorFactory。(第3步)

图14 加载RN代码
图15 读取bundle
图16 RCTWebSocketExecutor 发送命令给远端

6.执行rn代码

前面的准备工作都完成之后,就可以开始执行RN代码了,这里根据bundle 的类型(常规和ram两种)不同会有不同的执行方式,但总的来说都是先注册好回调事件,包括nativeFlushQueueImmediate和nativeCallSyncHook,以及nativeLoggingHook,然后通过javascriptcore执行rn的入口文件的代码。需要注意的是,javascriptCore执行js的容器是一个单独的JSGlobalContextRef,与webview的js容器是分开的,因此如果在RN里面嵌入webview,两边的js代码是不互通的,如果要通讯,需要借助javascriptCore桥接到原生代码并调用webview的evaluateScript来实现。

图17 执行RN代码
图18 加载代码并执行
图19 监听JS事件

图20 执行javascript代码

======================================================================

知识拓展:

智能指针std::shared_ptr

RN的代码里面大量用到了std::shared_ptr,这是一种智能指针,作用有如同指针,但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的引用计数(reference counting),比如我们把只能指针赋值给另外一个对象,那么对象多了一个智能指针指向它,所以这个时候引用计数会增加一个,我们可以用shared_ptr.use_count()函数查看这个智能指针的引用计数,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。

shared_ptr 一般使用make_shared 方法初始化,这样不用分配两次内存,效率会比较高。

使用智能指针可以方便资源的管理,自动释放没有指针引用的对象。

你可能感兴趣的:(RN iOS端启动过程解析)