美文网首页React Native开发
React Native iOS端启动过程解析

React Native iOS端启动过程解析

作者: FingerStyle | 来源:发表于2019-10-31 15:39 被阅读0次

    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.初始化Bridge 。注册module之后,RCTCxxBridge会创建一个executorFactor,这是一个工厂类,用于生成执行JS方法的类。 然后,RCTCxxBridge开始创建bridge 。

    图6 创建executorFactory及初始化bridge

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

    图 7 创建js 线程

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

    图8  createNativeModule

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

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

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

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

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

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

    图12-2 JSToNativeBridge

    4.加载rn代码。这一步跟初始化Bridge是同时进行的,由一个dispatch_group进行管理。这一步会调用RCTJavaScriptLoader加载bundle。这里会先尝试同步从本地读取bundle,如果不存在或者不能同步读取,则尝试异步读取。异步读取也是先从本地读,读不到则去远程服务器上获取。

    图13 加载RN代码 图14 读取bundle

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

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

    知识拓展:

    智能指针std::shared_ptr

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

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

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

    --------------补充--------------

    关于JS如何回调OC的代码,我之前一直有个理解上的误区,以为RN这边有一个类实现了JSExport协议,JS那边通过这里面的方法调到OC来,但其实我错了。有一个很关键的点忽略了,就是nativeFlushQueueImmediate这个方法,这其实是JS那边的一个全局属性(注意setProperty方法),JS调用这个方法,然后通过JavaScriptCore的api,触发了iOS这边的回调函数,注意看这一段:

    nativeFlushQueueImmediate方法

    这个就是nativeFlushQueueImmediate对应的回调函数([this]后面小括号里面那一段)。这里调用了callNativeModules方法,其中args里面保存了要执行的方法队列,这里取出队列的第一个元素,即我们需要立即执行的方法(对应的moduleId、methodId、params等参数),用于查找OC这边对应的方法并执行。 

         这个calNativeModules函数有很多地方都有调用, 除了nativeFlushQueueImmediate之外另外一处就是在RCTObjcExecutor 对象初始化时注册在runloop里面的,并且是在m_jsThread( MessageQueueThread类型)这个对象上执行的。这段代码的意义在于利用runloop的间隔不断的从js线程中取出我们要执行的方法,并调用原生对应模块执行,避免js线程阻塞。

    关于js调原生的逻辑,可以参考这个博客(是安卓端的,不过大体流程一致):

    https://www.jianshu.com/p/4ee10eaf352b

    相关文章

      网友评论

        本文标题:React Native iOS端启动过程解析

        本文链接:https://www.haomeiwen.com/subject/yajtbctx.html