react-native源码阅读基础篇-NativeModule
我们在使用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的结构我概括成这么一个模型。数组(注册表里存的就是转换后的模型信息)
从原生模块的源码里转换出原生模块的注册信息