找到所有的模块

一般来说,只要在模块中声明 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];

参考

RN 中 Native 模块的注入过程的更多相关文章

  1. Android FM学习中的模块 FM启动过程

    最近的研究FM模,FM是一家值我正在学习模块.什么可以从上层中可以看出. 上层是FM按钮的操作和界面显示,因此调用到FM来实现广播收听的功能. 看看Fm启动流程:例如以下图: 先进入FMRadio.j ...

  2. React Native(十五)——RN中的分享功能

    终于,终于,可以总结自己使用RN时的分享功能了-- 为什么呢?且听我慢慢道来吧: 从刚开始接触React Native(2017年9月中旬)就着手于分享功能,直到自己参与公司的rn项目开发中,再到现在 ...

  3. ABP中的模块初始化过程(一)

    在总结完整个ABP项目的结构之后,我们就来看一看ABP中这些主要的模块是按照怎样的顺序进行加载的,在加载的过程中我们会一步步分析源代码来进行解释,从而使自己对于整个框架有一个清晰的脉络,在整个Asp. ...

  4. Android studio中为项目添加模块依赖的过程

    https://blog.csdn.net/cheng__lu/article/details/74574582 Android studio中为项目添加模块依赖的过程 1.点击菜单file>p ...

  5. node 中第三方模块的加载过程原理

    node 中第三方模块的加载过程原理 凡是第三方模块都必须通过 npm 来下载 使用的时候就可以通过require('包名') 的方式来进行加载才可以使用 不可能有任何一个第三方包和核心模块的名字是一 ...

  6. 从源码看 angular/material2 中 dialog模块 的实现

    本文将探讨material2中popup弹窗即其Dialog模块的实现. 使用方法 引入弹窗模块 自己准备作为模板的弹窗内容组件 在需要使用的组件内注入 MatDialog 服务 调用 open 方法 ...

  7. Java EE中的容器和注入分析,历史与未来

    Java EE中的容器和注入分析,历史与未来 java中的容器 java中的注入 容器和注入的历史和展望 一.java中的容器 java EE中的注入,使我们定义的对象能够获取对资源和其他依赖项的引用 ...

  8. tensorflow中slim模块api介绍

    tensorflow中slim模块api介绍 翻译 2017年08月29日 20:13:35   http://blog.csdn.net/guvcolie/article/details/77686 ...

  9. 《React-Native系列》3、RN与native交互之Callback、Promise

    接着上一篇<React-Native系列>RN与native交互与数据传递,我们接下来研究另外的两种RN与Native交互的机制 一.Callback机制 首先Calllback是异步的, ...

随机推荐

  1. interrupt()方法的简单理解

    interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程.这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态.更确切的说,如果线程被Objec ...

  2. Python全栈工程师(Python3 所有基础内容 0-0)

    ParisGabriel              每天坚持手写  一天一篇  决定坚持几年 为了梦想为了信仰    开局一张图     Python一个月的基础语法 基本就到这咯    接下来是数据 ...

  3. jQuery中mouseover和mouseout冒泡产生闪烁问题

    问题:在jQuery中,对元素绑定mouseover和mouseout事件时,每次移入移出该元素和子元素时,都会触发事件,从而会出现闪动的现象. 原因:浏览器的冒泡行为. 解决方案: 使用mousee ...

  4. cin和gitchar的区别

    cin是iostream(输入输出类) 类下的istream(输入类)类的对象,作用是顺序输入字符串.cin.get()是cin的方法.cin.get()是C++面向对象的操作,getchar()是C ...

  5. 通过HttpClient请求webService

    通过HttpClient请求webService 由于服务端是用webService开发的,android要调用webService服务获取数据,这里采用的是通过HttpClient发送post请求, ...

  6. 基于jCOM搭建Java-微软信息桥梁(下)

    第一部分析了BEA提供的Java/COM互操作解决方案—jCOM的实现原理:本文是第二部分,比较全面地分析了Weblogic Server的jCOM实现技术之后,通过一个具体实例来说明了jCOM的具体 ...

  7. linux系统上查看硬件信息

    一:查看CPU more /proc/cpuinfo | grep "model name" grep "model name" /proc/cpuinfo 如 ...

  8. [转载]MVC、MVP以及Model2(下)

    通过采用MVC模式,我们可以将可视化UI元素的呈现.UI处理逻辑和业务逻辑分别定义在View.Controller和Model中,但是对于三者之间的交互,MVC并没有进行严格的限制.最为典型的就是允许 ...

  9. div自适应宽度

    对于div自适应宽度,网上的说法基本上都是将要自适应宽度的div放在其它固定宽度的最后,不指定其float属性或将float属性指定为none,比如三栏布局居中的一栏为自适应宽度,就可以这样来定义三栏 ...

  10. Angularjs 分页控件

    实现效果: 实现步骤: 1.分页页面:page.html,页面多余样式,需要自己去除. <div class="row" ng-show="conf.totalIt ...