美文网首页
react-native剖析之ios解析导出原生module

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

作者: 追梦3000 | 来源:发表于2017-10-29 14:47 被阅读75次

    前言

    我们知道,在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.png

    1、关于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。

    相关文章

      网友评论

          本文标题:react-native剖析之ios解析导出原生module

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