iOS端RN复杂组件的设计和原理分析

1,380 阅读6分钟
原文链接: mp.weixin.qq.com

前言    

      RN的开发思路是通过组合各种组件来组织页面,大部分情况下通过组合视图,文本和图片等几个基础组件,就可以非常方便的实现各种复杂的跨平台组件,不过在需要原生功能支持、组件较为复杂、或者对性能有要求的情况下还是需要进行一定的原生的开发,合理的组件实现方式可以降低使用和跨平台的成本。

      在前段时间同镇C端的RN业务开发过程中,需要开发一套视频播放的RN 组件,基于目前同城的native视频播放组件,在RN页面上进行业务扩展,在native设计组件和RN端调用组件的属性和方法的过程中遇到了一些坑,也借此机会了解了iOS 端对复杂RN组件的设计以及RN调用组件的属性和方法时的底层源码实现。

两种NativeModule

      我们目前的58 同镇的业务需求中,有很多是RN的需求,在 RN调用NativeModule 时,我们可以分为两种类型:

       1. 源生API 模块(可称为APIModule)

      APIModule是一种面向过程式的模块调用,阐述了JS是如何调用native各个模块的逻辑,JS 只需要用一个模块名,一个API名,就能通过bridge找到对应的native方法进行调用(JS Call OC Method),这个过程就是一个函数的调用,不存在操作某个实例对象,只是传递参数,操作参数,处理逻辑,返回数值或者处理回调。

比如展示toast 的APIModule:

       这种APIModule目前在我们业务开发中比较常用也是应用最多的,这个不是本文的重点,主要看一下源生UI组件模块。

        2. 源生 UI组件模块(可称为 UIModule)

      UIModule是一种面向对象式的UI组件调用,阐述了JS是如何创建native的UI 界面。每一个被RN渲染出来的RCTView都是继承自UIView的纯源生UI组件,RCTView 与Component是一一对应的。

       UIModule其实由两个部分组成,RCTView 和RCTViewManager,类似于view和 controller的关系,每个UIModule都会有一个RCTComponentData 与之配合。RN提供的源生基础组件View, Text,Image等在native 都有对应的UIModule。

View对应RCTView(继承UIView)与RCTViewManager

Image对应RCTImageView(继承UIImageView)与RCTImageViewManager

Text 对应RCTTextView(继承RCTView)与RCTTextViewManager

ScrollView 对应RCTScrollView(继承RCTView)与RCTScrollViewManager

每一个JS的Component与之直接配合的都是RCTViewManager,这是一个继承自NSObject遵从RCTBridgeModule协议的类,我们如果自定义一个UIModule,也是需要继承自RCTViewManager,他只是一个控制器的角色,因此RCTViewManager还需要决定他采用什么方案进行绘制,所以在RCTViewManager可以选择使用不同的UIView来实现真正的视图角色。

如何定义一个UI组件module

       每个 UIModule需要一个RCTView 和RCTViewManager与之对应, iOS端一个完整的UIModule 定义流程为

下面是视频组件的RCTViewManager

可以看到,无论是APIModule或者UIModule,都会有一个宏:RCT_EXPORT_MODULE();  那么这个宏起到什么作用呢,我们进入宏定义可以看到:

RCTRegisterModule方法里创建RCTModuleClasses表,并将所有注册的     module(Module和ViewManager )存入表里,宏里面实现了两个方法,一个是自动写好了+moduleName的实现,返回了@#js_name, @#的意思是自动把宏的参数js_name转成字符。

另一个是自动写了+load的实现, +load大家都知道,app一运行就会执行一次,所有的类都会执行一次,所以在 app运行的时候,这个module类就会自动执行 RCTRegisterModule这个函数,这个函数干了些什么事情呢?首先在内存中创建了一个单例RCTModuleClasses 表,然后判断你写的类是否遵从RCTBridgeModule协议(这也是为什么要求你在写module 定义的时候一定要遵从协议),然后把你写的moduleClass放入内存的单例RCTModuleClasses 表中。

而定义属性的宏RCT_EXPORT_VIEW_PROPERTY 可以看里面的实现:

可以看到是拼接了一个方法,具体是干什么的呢,后面会解释。

RN端的调用

     iOS端创建完组件后, RN端是怎么调用的呢?

     首先需要调用requireNativeComponent方法获取 native组件,并声明native组件的属性和方法回调,如下图:

我们可以看一下RN的源码,在源码里面的这个方法到底干了什么,又是怎么去获取 native封装好的组件的。

如上图,调用requireNativeComponent方法时传入两个参数(看源码实际有三个参数,第三个为额外的属性配置),第一个是组件名,第二个是组件实例,      requireNativeComponent方法里通过组件名从UIManager(对应native 的RCTUIManager)中得到native注册的的组件的组件配置 viewConfig,其中包括native注册组件时的属性、方法回调、以及自定义的方法。

如上图所示,requireNativeComponent方法最后,在对 viewConfig进行一些处理之后调用createReactNativeComponentClass方法,并将 viewConfig传入,方法里根据viewConfig创建组件实例 component,通过构造器new了一个ReactNativeBaseComponent ,源码如下图:

然后跟踪ReactNativeBaseComponent ,文件里基本是生命周期方法和构造方法,可以看到生命周期方法mountComponent方法中创建组件时调用了 UIManager.createView()方法,即native 的RCTUIManager给 RN暴露的createView 方法。

所以回到iOS端的RCTUIManager ,在createView的方法实现里,根据传入的  reactTag 获取创建组件并给组件的属性赋值。

在获取view时通过 createViewWithTag方法,可以看该方法的实现,实际是调用[self.manager view]方法,即在定义 native组件时,RCTxxManager里重写父类的 view方法返回的自定义native组件。

 

组件在赋值属性的时候调用的componentData setProps forView 方法,点进去追溯方法实现可以看到如下图所示:

方法中会判断manager中是否实现 propConfig_属性名的方法,也就是上文中提到的RCTxxManager中属性宏定义的方法,然后对声明注册的属性进行赋值。这样,赋值完属性的组件就被构建出来并且被存储到 RCTUIManager的_viewRegistry中。

属性的改变和同步

      当组件重新 render时会调用ReactNativeBaseComponent 下的receiveComponent生命周期方法,在该方法中调用 native的RCTUIManager 的updateView方法去更新组件的属性。如下图:

而在iOS端的 RCTUIManager中,可以看到updateView方法中根据 reactTag给对应的组件属性赋值,属性赋值方法和createView中的一致。

总结:

      本文讲述了iOS端 RN组件的设计,以及RN端调用native 组件时,从源码上解析组件的构建和属性赋值的过程,在了解了RN端和native 端互相调用的过程之后,才能针对一些复杂RN组件的业务场景处理对应的业务逻辑。