找到所有的模块

一般来说,只要在模块中声明 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 这个函数返回。
在 RCTCxxBridge 的 start 函数中,会调用 _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];

如有必要,调用 RCTModuleData 的 instance 方法。

初始化 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];
}

方法的注册

通过 RCTModuleData 的 methods 方法来注册进入。

  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 是这个生成函数, _moduleClass 是 self 变量, 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. basicHttpBinding

    表示一个绑定,Windows Communication Foundation (WCF) 服务可以使用此绑定配置和公开能够与基于 ASMX 的 Web 服务和客户端通信的终结点以及符合 WS-I B ...

  2. R学习笔记-安装R和RStudio,注意RStudio的版本需要与操作系统版本匹配

    1.安装步骤:先安装R,再安装RStudio RStudio是R的集成开发工具,本身不带R环境. 2.从当前R的官网和RStudio下载的R和RStudio的版本分别为: A .For Windows ...

  3. Qt的安装和使用中的常见问题(简略版)

    对于喜欢研究细节的朋友,可参考Qt的安装和使用中的常见问题(详细版). 目录 1.引入 2.Qt简介 3.Qt版本 3.1 查看安装的Qt版本 3.2 查看当前项目使用的Qt版本 3.3 查看当前项目 ...

  4. ubuntu登陆无限循环

    现象:在Ubuntu登陆界面输入密码之后,黑屏一闪并且出现了check battery state之类的文字之后,又跳转到登录界面. 原因:主目录下的.Xauthority文件拥有者变成了root,从 ...

  5. python进行数据清理之pandas中的drop用法

    好久好久没有更新博客了,之前自学的估计也都忘记差不多了.由于毕业选择从事的行业与自己的兴趣爱好完全两条路,心情也难过了很久,既然入职了就要好好干,仍要保持自己的兴趣,利用业余时间重拾之前的乐趣. 从基 ...

  6. 数据挖掘算法以及其实现zz

    实验一    分类技术及其应用 实习要求: 基于线性回归模型拟合一个班学生的学习成绩,建立预测模型.数据可由自己建立100个学生的学习成绩. 1)    算法思想: 最小二乘法 设经验方程是y=F(x ...

  7. sqlserver怎么将excel表的数据导入到数据库中

    在数据库初始阶段,我们有些数据在EXCEL中做好之后,需要将EXCEL对应列名(导入后对应数据库表的字段名),对应sheet(改名为导入数据库之后的表名)导入指定数据库, 相当于导入一张表的整个数据. ...

  8. [GO]redis的连接

    package main import ( "github.com/garyburd/redigo/redis" "fmt" ) var pool *redis ...

  9. TCP的几个状态(SYN/FIN/ACK/PSH/RST)

    在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG. 其中,对于我们日常的分析有用的就是前面的五个字段. 含义: SYN 表示建立连接, ...

  10. [label][paypal] Paypal 支付页面的语言显示问题

    答复 Frank 通过 Email2014-11-12 02:01 上午 您好 感谢您联系PayPal商家技术支持中心. PayPal登陆页面的语言是PayPal服务器根据以下各种因素来自动判断的:1 ...