RN 中 Native 模块的注入过程

找到所有的模块

一般来说,只要在模块中声明 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转成字符,但我们刚才的样例里,都是直接不写参数的注册宏,所以说如果注册的时候不写参数,+moduleName会返回空

+load 函数被调用的时机很早。下面我们看 RCTRegisterModule 这个函数。

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

大致是把把所有模块都加入了一个 RCTModuleClasses 的数组中,被 RCTGetModuleClasses 这个函数返回。
RCTCxxBridgestart 函数中,会调用 _initModulesWithDispatchGroup函数,其中会遍历 RCTModuleClasses 这个数组。

对每一个模块进行处理

找到模块的 name

如下所示,先看宏中定义的 moduleName 有没有返回名字(没有),如果没有,就取模块所在类的名字,然后去掉前缀。

 NSString *RCTBridgeModuleNameForClass(Class cls)
{
#if RCT_DEBUG
  RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
            @"Bridge module `%@` does not conform to RCTBridgeModule", cls);
#endif

  NSString *name = [cls moduleName];
  if (name.length == 0) {
    name = NSStringFromClass(cls);
  }

  if ([name hasPrefix:@"RK"]) {
    name = [name substringFromIndex:2];
  } else if ([name hasPrefix:@"RCT"]) {
    name = [name substringFromIndex:3];
  }

  return name;
} 

处理 moduleDataByName 变量

moduleDataByName 的是一个字典,key 是字符串,value 是 RCTModuleData
最开始这字典是空,为每一个模块,都生成一个 RCTModuleData,然后加到字典中。

moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
                                                 bridge:self];
moduleDataByName[moduleName] = moduleData;

然后把模块所在的类和 RCTModuleData 都加到一个数组中。

[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];

如有必要,调用 RCTModuleDatainstance 方法。

初始化 RCTModuleData

RCTModuleData 有若干变量。

instance 初始化

是遵守 RCTBridgeModule 协议的类型,被 _moduleProvider 这个 block 创建。

 _instance = _moduleProvider ? _moduleProvider() : nil;

这个 block 在初始化时被创建。因此 RCTTextManager 对应的 RCTModuleData,它的 _instance 就是 RCTModuleData 对应的实例变量。

- (instancetype)initWithModuleClass:(Class)moduleClass
                             bridge:(RCTBridge *)bridge
{
  return [self initWithModuleClass:moduleClass
                    moduleProvider:^id<RCTBridgeModule>{ return [moduleClass new]; }
                            bridge:bridge];
}

instance 设置 bridge 变量

设置为 RCTCxxBridge

  [(id)_instance setValue:_bridge forKey:@"bridge"];

instance 设置 methodQueue 变量

这个模块暴露给 JS 的方法,都在 methodQueue 声明的队列中被调用。如果不声明,那么没有给模块默认生成一个队列。

BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
if (implementsMethodQueue && _bridge.valid) {
  _methodQueue = _instance.methodQueue;
}
if (!_methodQueue && _bridge.valid) {
  // Create new queue (store queueName, as it isn't retained by dispatch_queue)
  _queueName = [NSString stringWithFormat:@"com.facebook.react.%@Queue", self.name];
  _methodQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL);

  // assign it to the module
  if (implementsMethodQueue) {
    @try {
      [(id)_instance setValue:_methodQueue forKey:@"methodQueue"];
    }

通过 bridge 为每一个模块注册帧更新时的调用

bridge 有一个 CADisplayLink,在 JS 线程中每秒更新 60 次。
如果这个模块实现了 RCTFrameUpdateObserver 协议,那么在每次调用 _jsThreadUpdate 时,会调用这个模块的 didUpdateFrame 方法。

- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
                       withModuleData:(RCTModuleData *)moduleData
{
  [_displayLink registerModuleForFrameUpdates:module withModuleData:moduleData];
}

方法的注册

通过 RCTModuleDatamethods 方法来注册进入。

  Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);

  for (unsigned int i = 0; i < methodCount; i++) {
    Method method = methods[i];
    SEL selector = method_getName(method);
    if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
      IMP imp = method_getImplementation(method);
      NSArray *entries =
        ((NSArray *(*)(id, SEL))imp)(_moduleClass, selector);
      id<RCTBridgeMethod> moduleMethod =
        [[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
                                            JSMethodName:entries[0]
                                                  isSync:((NSNumber *)entries[2]).boolValue
                                             moduleClass:_moduleClass];

      [moduleMethods addObject:moduleMethod];
    }
  }

  free(methods); 

先找到模块所有的函数,然后遍历所有的函数名,看有没有 __rct_export__ 前缀。如果有,就生成一个 RCTModuleMethod 变量,并加入到 moduleMethods这个数组里,这个数组是这个模块暴露的 JS 的所有可调用方法。

RCT_EXPORT_METHOD

暴露给 JS 的函数,要用 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) \
+ (NSArray *)RCT_CONCAT(__rct_export__, \
    RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
    return @[@#js_name, @#method, @is_blocking_synchronous_method]; \
}  

下面通过一个例子来说明

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)

第一次展开之后,如下

_RCT_EXTERN_REMAP_METHOD(, addEvent:(NSString *)name location:(NSString *)location, NO) \
- (void)addEvent:(NSString *)name location:(NSString *)location;  

把第一行继续展开,如下。一个例子是 __rct_export__1180

+ (NSArray *)__rct_export__ RCT_CONCAT(__LINE__, __COUNTER__) { \
    return @[@"", @"addEvent:(NSString *)name location:(NSString *)location", @NO]; \
}  

即生成一个唯一的函数,以 __rct_export__ 开头,以当前行数结尾。
返回是一个数组,第一个值是对应的 js 名字(这里是空),第二个值是方法名,第三个值是是否同步调用。
可以和 methods 方法中的函数调用对应起来。imp 是这个生成函数, _moduleClassself 变量, selector 是方法名。这个是 OC 消息传递机制的特性。

NSArray *entries =    ((NSArray *(*)(id, SEL))imp)(_moduleClass, selector);

根据返回数组生成一个 RCTModuleMethod,如下。第一个元素对应 JS 方法签名,第二个元素对应 OC 的方法签名。

[[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
                                    JSMethodName:entries[0]
                                          isSync:((NSNumber *)entries[2]).boolValue
                                     moduleClass:_moduleClass];

参考

posted on 2017-08-20 17:44  花老🐯  阅读(713)  评论(0编辑  收藏  举报

导航