专栏首页TechBox详解RN中native module暴露原理

详解RN中native module暴露原理

2860元腾讯云代金券免费领取,付款直接抵现金,立即领取>>>

腾讯云海外服务器1折限时抢购,2核4G云主机768元/1年,立即抢购>>>

腾讯云服务器1折限时抢购,2核4G云主机899元/3年,立即抢购>>>

我们知道,RN可以调用Native侧的方法。并且RN框架也给我们提供了这一能力,只要我们按照某些约定在native侧实现一个方法,那么就可以在JS侧顺利调用。如下实现了一个简单的native模块:

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

@interface NativeLogModule : NSObject<RCTBridgeModule>

@end

#import "NativeLogBridge.h"

@implementation NativeLogModule
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(nativeLog:(id)obj) {
  NSLog(@"开始输出日志:");
  NSLog(@"%@",obj);
  NSLog(@"日志输出完毕!");
}
@end

?

以上是一段OC写的native代码,NativeLogModule遵守了RN提供的协议RCTBridgeModule。该协议中规定了一些宏和方法。遵守了这个协议,NativeLogModule就可以使用RCT_EXPORT_MODULE()宏将该类以module的方式暴露给JS,然后使用RCT_EXPORT_METHOD将native方法暴露给JS。接下来看下JS侧是怎么调用NativeLogModule的nativeLog方法。

import { NativeModules } from 'react-native';

NativeModules.NativeLogModule.nativeLog('记录点什么吧');

以上两行JS即可实现native方法的调用,第一行是导入NativeModules模块,第二行通过NativeModule调用NativeLogModule

的nativeLog方法。以上即可实现JS调用Native方法。但在学习RN之初,想必大家都有一个疑问,Native方法是怎么暴露给JS的呢?JS又是怎么调用这些Native方法的呢?

这里就不得不说RN中的两个宏了,RCT_EXPORT_MODULE 和 RCT_EXPORT_METHOD。

RCT_EXPORT_MODULE(Native模块暴露给JS)

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

?

如上代码所示,RCT_EXPORT_MODULE宏背后是两个静态方法+(NSString *)moduleName和+(NSString *)load。moduleName方法简单的返回了native模块的类名,如果RCT_EXPORT_MODULE宏的参数是空,就默认导出类名作为模块名,如果参数不是空,就以参数名为模块名。load方法是大家耳熟能详的的,load方法调用RCTRegisterModule函数注册了模块。

让我们再来看看RCTRegisterModule函数的实现(该函数定义在RCTBridge.m中):

static NSMutableArray<Class> *RCTModuleClasses;

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];
  });
}

很简单,RCTRegisterModule函数只做了3件事:

1.创建一个全局的可变数组和一个队列(如果数组还不存在)

2.检查导出给JS模块是否遵守了RCTBridgeModule协议

3.把要导出的类添加到全局的可变数组中进行记录

可见,在app启动后调用load方法时,所有需要暴露给JS的方法都已经被注册到一个数组中。到此为止,只是把需要导出给JS的类记录下来了,那这些类又是在什么阶段提供给JS的呢?Native侧全局搜索RCTGetModuleClasses函数,可以看到该函数在RCTCxxBridge的start中被调用了:

- (void)start
{
  // 此处省略若干行...
  [self registerExtraModules];
  // Initialize all native modules that cannot be loaded lazily
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  [self registerExtraLazyModules];
  // 此处省略若干行...
}

?

然后每个native module都会被转换成RCTModuleData对象,RCTModuleData描述了这个module的具体信息,moduleData会被缓存在_moduleDataByName字典中和_moduleDataByID数组,以及_moduleClassesByID数组中。至此,我们就已经在运行collect了所有需要导出给JS的native module。

- (NSArray<RCTModuleData *> *)_registerModulesForClasses:(NSArray<Class> *)moduleClasses
                                        lazilyDiscovered:(BOOL)lazilyDiscovered
{

  NSArray *moduleClassesCopy = [moduleClasses copy];
  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count];
  for (Class moduleClass in moduleClassesCopy) {
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
    RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self];

    _moduleDataByName[moduleName] = moduleData;
    [_moduleClassesByID addObject:moduleClass];
    [moduleDataByID addObject:moduleData];
  }
  [_moduleDataByID addObjectsFromArray:moduleDataByID];

  return moduleDataByID;
}

?

RCT_EXPORT_METHOD(Native方法暴露给JS)

#define RCT_EXPORT_METHOD(method) \
  RCT_REMAP_METHOD(, method)
  
#define RCT_REMAP_METHOD(js_name, method) \
 _RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
 - (void)method RCT_DYNAMIC;

#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; \
  }

#define RCT_CONCAT2(A, B) A ## B
#define RCT_CONCAT(A, B) RCT_CONCAT2(A, B)

?

通过上面一系列的宏调用不难看出,RCT_EXPORT_METHOD最终做了2件事:

1.定义一个对象方法

2.定义一个静态方法,该方法名的格式是+(const RCTMethodInfo *)__rct_export__+js_name+___LINE__+__COUNTER__

如果没有js_name参数,那么最终的格式就是+(const RCTMethodInfo *)__rct_export__+___LINE__+__COUNTER__

比如:+(const RCTMethodInfo *)__rct_export__131 其中13是__LINE__,1是__COUNTER__

而这个方法的实现是这样的:

+(const RCTMethodInfo *)__rct_export__+js_name+__LINE__+__COUNTER__ {
    static RCTMethodInfo config = {
        js_name,
        method,
        is_blocking_synchronous_method
    }
    return &config;
}

可以看出,最终把这个方法包装成了一个RCTMethodInfo对象,在运行时RN会扫描所有导出的native module中以__rct_export__开头的方法。

以上只是说了native module和method是如何导出的,这些模块和方法的注册将会在另一篇文章中介绍。

本文为原创文章,转载请获得授权。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • RN中native模块是如何暴露给JS的?

    我们知道,RN可以调用Native侧的方法。并且RN框架也给我们提供了这一能力,只要我们按照某些约定在native侧实现一个方法,那么就可以在JS侧顺利调用。如...

    VV木公子
  • 类的本质-类对象前言1.本质2.如何获取类对象3.类对象的用法4.类对象的存储5.OC实例对象、类对象、元数据、之间关系6.如何查询类型信息

    VV木公子
  • 模块化与解耦简述4. 解耦与通信5. 源码推荐

    VV木公子
  • RN中native模块是如何暴露给JS的?

    我们知道,RN可以调用Native侧的方法。并且RN框架也给我们提供了这一能力,只要我们按照某些约定在native侧实现一个方法,那么就可以在JS侧顺利调用。如...

    VV木公子
  • 挖一挖贝塞尔曲线那些事 原

    ????? 贝塞尔曲线的最初设计是服务于工业设计,尤其应用与汽车曲线设计。随着计算机画图的应用广泛,若想在计算机上画出平滑精准的曲线并不是一件容易的事,贝塞尔曲...

    珲少
  • 制造企业网站升级必看

    制造企业网站升级目的,一般是为了突出企业形象,打造企业品牌。通常企业网站展示企业实力和商品信息比较常见,在公司业绩发展迅速的同时,企业官网作为企业线上业务的主要...

    悉知科技建站
  • 第193天:js---Math+Error+Number+Object总结

    半指温柔乐
  • js引擎v8源码分析之GlobalHandles(基于v8 0.1.5)

    GlobalHandles是实现v8中持久句柄功能的类。GlobalHandles主要是维护一个链表,每个节点维护堆对象的状态。我们先看看节点的定义。

    theanarkh
  • MemCache在win7上的可视化配置以及Nodejs/Net应用

    惯例科普:MemCache是一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,但目前被许多网站使用以提升网站的访问速度,...

    Java中文社群_老王
  • 纪念晶体管诞生71周年——改变世界30款芯片大阅兵!

    1947年12月23日,第一个基于锗半导体的具有放大功能的点接触式晶体管面世,标志着现代半导体产业的诞生和信息时代正式开启。

    新智元

扫码关注云+社区

领取腾讯云代金券

http://www.vxiaotou.com