前言
我们知道,在ios的react-native开发中,可以通过自定义原生模块,来实现js调用原生功能。
先看下步骤:
1、创建类TestModule实现协议RCTBridgeModule
2、在.m文件中新增RCT_EXPORT_MODULE()
3、如要导出方法给js用,那么新增:
RCT_EXPORT_METHOD(add:(int)a b:(int)b callback:(RCTResponseSenderBlock)callback){
callback(@[ [NSNumber numberWithInt: a+b ] ]);
}
大概的代码为:
TestModule.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface TestModule : NSObject<RCTBridgeModule>
@end
TestModule.m
@implementation TestModule
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(add:(int)a b:(int)b callback:(RCTResponseSenderBlock)callback){
callback(@[ [NSNumber numberWithInt: a+b ] ]);
}
@end
这里react-native是经过了怎样的解析过程呢?
分析
RCT_EXPORT_MODULE做了什么
查看RCT_EXPORT_MODULE源码:
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
RCTRegisterModule
void RCTRegisterModule(Class);
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
});
RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
moduleClass);
// Register module
[RCTModuleClasses addObject:moduleClass];
}
这段代码很好理解,将模块的Class增加到全局数组中
RCT_EXTERN
#if defined(__cplusplus)
#define RCT_EXTERN extern "C" __attribute__((visibility("default")))
#define RCT_EXTERN_C_BEGIN extern "C" {
#define RCT_EXTERN_C_END }
#else
#define RCT_EXTERN extern __attribute__((visibility("default")))
#define RCT_EXTERN_C_BEGIN
#define RCT_EXTERN_C_END
#endif
这段就不那么好理解了,这里涉及三个宏
1、#if defined(__cplusplus) 判断是不是c++语言
2、attribute
参考文章:c语言中attribute的意义
3、attribute((visibility("default")))
参考这里
这里简单的理解为,外部可见,作用和原版的extern没有什么区别。
将上面的宏定义组装一下:
如果对宏不熟悉,可以先看看这里
extern __attribute__((visibility("default"))) void RCTRegisterModule(Class);
+ (NSString *)moduleName { return @"TestModule" ;}
+ (void)load { RCTRegisterModule(self); }
将这段代码替换RCT_EXPORT_MODULE()
并在这两个函数调用这里下个断点,在编译运行,马上就看出是怎么加载的。
image.png1、关于load函数,请看这里细说OC中的load和initialize方法
在load里面已经调用RCTRegisterModule注册了本Class
2、关于如何初始化加载本模块
image.png从调用堆栈上可以看出一些端倪,下面一个个分析。
(1)、首先是这里初始化整个react视图
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"sc"
initialProperties:nil
launchOptions:launchOptions];
(2)、比较关键的代码
// Set up moduleData for automatically-exported modules
NSArray<RCTModuleData *> *moduleDataById = [self registerModulesForClasses:modules];
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClasses.count];
for (Class moduleClass in moduleClasses) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
// Don't initialize the old executor in the new bridge.
// TODO mhorowitz #10487027: after D3175632 lands, we won't need
// this, because it won't be eagerly initialized.
if ([moduleName isEqualToString:@"RCTJSCExecutor"]) {
continue;
}
// Check for module name collisions
RCTModuleData *moduleData = _moduleDataByName[moduleName];
if (moduleData) {
if (moduleData.hasInstance) {
// Existing module was preregistered, so it takes precedence
continue;
} else if ([moduleClass new] == nil) {
// The new module returned nil from init, so use the old module
continue;
} else if ([moduleData.moduleClass new] != nil) {
// Both modules were non-nil, so it's unclear which should take precedence
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
"name '%@', but name was already registered by class %@",
moduleClass, moduleName, moduleData.moduleClass);
}
}
//这里将模块有关的信息先封装起来,以便以后懒加载调用
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self];
_moduleDataByName[moduleName] = moduleData;
[_moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}
[_moduleDataByID addObjectsFromArray:moduleDataByID];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return moduleDataByID;
RCT_EXPORT_METHOD 做了什么
先跟踪看看源码:
#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;
#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)
注意这里COUNTER是计数器,每次使用都会加1,LINE为行号
这里照样将上面的宏定义组装一下
+ (const RCTMethodInfo *)__rct_export__行号计数器号{
static RCTMethodInfo config = {"", "add:(int)a b:(int)b callback:(RCTResponseSenderBlock)callback", NO};
return &config;
}
-(void)add:(int)a b:(int)b callback:(RCTResponseSenderBlock)callback;
这里的rct_export函数的作用是利用NSStringFromSelector找到对应的selector,以便将来可以执行这个函数。
总结
RCT_EXPORT_MODULE 这个宏用于让系统知道有这么个类,RCT_EXPORT_METHOD这个宏用于让系统知道这个类有哪些方法是可以调用的,并且获取了这个方法的selector。
网友评论