react-native剖析之ios解析导出原生module

前言

我们知道,在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 
#import 
@interface TestModule : NSObject

@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()

并在这两个函数调用这里下个断点,在编译运行,马上就看出是怎么加载的。

react-native剖析之ios解析导出原生module_第1张图片
image.png

1、关于load函数,请看这里细说OC中的load和initialize方法
在load里面已经调用RCTRegisterModule注册了本Class

2、关于如何初始化加载本模块

react-native剖析之ios解析导出原生module_第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 *moduleDataById = [self registerModulesForClasses:modules];

 NSMutableArray *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。

你可能感兴趣的:(react-native剖析之ios解析导出原生module)