美文网首页
react-native源码阅读基础篇-NativeModule

react-native源码阅读基础篇-NativeModule

作者: nextChallenger | 来源:发表于2020-12-02 15:47 被阅读0次

    我们在使用rn开发的过程,有些需求可能需要封装原生模块,然后提供给js端使用。
    那么在rn中,Native端是怎么提供模块给js端使用的呢?
    这个问题有点大,流程也特别长,我们的最终目的是要梳理清楚这个。
    我们在这之前先看看一些基础细节,
    这篇文档 我们一起学习一下NativeModule的结构

    例子

    我们先看官方提供的原生模块的例子
    头文件里遵循RCTBridgeModule协议

    // CalendarManager.h
    #import <React/RCTBridgeModule.h>
    
    @interface CalendarManager : NSObject <RCTBridgeModule>
    @end
    

    使用RCT_EXPORT_MODULE()这个宏,标记当前模块要暴露给js使用。
    使用RCT_EXPORT_METHOD()这个宏,将方法暴露给js端使用

    #import "CalendarManager.h"
    #import <React/RCTLog.h>
    
    @implementation CalendarManager
    
    RCT_EXPORT_MODULE();
    
    RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
    {
      RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
    }
    
    @end
    

    在js端就可以使用日历模块了

    import { NativeModules } from 'react-native';
    const CalendarManager = NativeModules.CalendarManager;
    CalendarManager.addEvent(
      'Birthday Party',
      '4 Privet Drive, Surrey'
    );
    

    除了有两个宏,貌似和原生代码没有多少的差别。也就是说核心是通过这两个宏来实现的。
    我们看一下这两个宏的实现

    1、第一个宏RCT_EXPORT_MODULE ()

    #define RCT_EXPORT_MODULE(js_name)          \
      RCT_EXTERN void RCTRegisterModule(Class); \
      +(NSString *)moduleName                   \
      {                                         \
        return @ #js_name;                      \
      }                                         \
      +(void)load                               \
      {                                         \
        RCTRegisterModule(self);                \
      }
    

    @# 的意思是自动把宏的参数 js_name 转成字符
    可以看到:

    • RCT_EXPORT_MODULE 包括一个RCTRegisterModule函数。字面意思注册模块。
    • 重写了+(NSString *)moduleName(返回模块名称)和+(void)load两个方法。
    • 这里还有一个新的宏RCT_EXTERN
    #define RCT_EXTERN extern __attribute__((visibility("default")))
    

    最终我们把宏展开

    //RCT_EXPORT_MODULE();
    extern __attribute__((visibility("default"))) void RCTRegisterModule(Class);
    +(NSString *)moduleName
    {
      return @"CalendarManager";
    }
    +(void)load
    {
      RCTRegisterModule(self);
    }
    

    + (void)load方法的调用时机是程序在启动的时候,加载类的时候,会执行。函数内部调用RCTRegisterModule方法注册模块。

    // RCTBridge.m
    void RCTRegisterModule(Class moduleClass)
    {
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
        RCTModuleClasses = [NSMutableArray new];
        RCTModuleClassesSyncQueue =
            dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);
      });
    
      RCTAssert(
          [moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
          @"%@ does not conform to the RCTBridgeModule protocol",
          moduleClass);
    
      // Register module
      dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
        [RCTModuleClasses addObject:moduleClass];
      });
    }
    

    总结一下RCT_EXPORT_MODULE ()做得事情就是app在刚启动加载类的时候,会注册一下当前类。所谓的注册本质上是往RCTModuleClasses里添加对应的类信息。这样在启动以后,所有暴露给js使用的原生模块就可以通过RCTModuleClasses获取到.

    static NSMutableArray<Class> *RCTModuleClasses;
    

    RCTModuleClasses就是个可变数组

    第二个宏 RCT_EXPORT_METHOD()

    // RCT_EXPORT_METHOD
    #define RCT_EXPORT_METHOD(method) RCT_REMAP_METHOD(, method)
    

    这个宏是对RCT_REMAP_METHOD的一个包装

    // RCT_REMAP_METHOD
    #define RCT_REMAP_METHOD(js_name, method)       \
      _RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
      -(void)method RCT_DYNAMIC;
    
    
    • 生成配置信息
    • 在方法名前补上返回值类型 -(void)
    // _RCT_EXTERN_REMAP_METHOD
    #define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method)                            \
      +(const RCTMethodInfo *)RCT_CONCAT(__rct_export__, RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) \
      {                                                                                                          \
        static RCTMethodInfo config = {#js_name, #method, is_blocking_synchronous_method};                       \
        return &config;                                                                                          \
      }
    

    创建并返回模块信息,包括:

    • 模块名称(js_name)
    • 方法名称(method)
    • 是否异步(is_blocking_synchronous_method)

    最终展开的效果

    // ------------------------------------------------展开前
    RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
    {
      RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
    }
    
    // ------------------------------------------------展开后
     + (const RCTMethodInfo *)__rct_export__390 {
       static RCTMethodInfo config = {
          "",
          "addEvent:(NSString *)name location:(NSString *)location",
          NO,
       };
       return &config;
     }
     - (void)addEvent:(NSString *)name location:(NSString *)location
     {
       RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
     }
    
    

    总结一下: RCT_EXPORT_METHOD宏实现的效果 是将暴露给js的方法,生成该方法的配置信息,同时创建对应的原生方法的实现。

    结论

    经过上述的简单梳理,我们可以判断。如果我们需要封装原生模块给js端使用。
    我们需要在原生模块内部生成一些配置信息,并将类信息存储到一个数组里面。这样后续可以通过这个数组获取到所有暴露给js使用的模块列表(这个数组也可以叫注册表)。

    单个NativeModule的结构我概括成这么一个模型。数组(注册表里存的就是转换后的模型信息)

    从原生模块的源码里转换出原生模块的注册信息

    相关文章

      网友评论

          本文标题:react-native源码阅读基础篇-NativeModule

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