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 这个函数返回。
在 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 模块的注入过程的更多相关文章
- Android FM学习中的模块 FM启动过程
最近的研究FM模,FM是一家值我正在学习模块.什么可以从上层中可以看出. 上层是FM按钮的操作和界面显示,因此调用到FM来实现广播收听的功能. 看看Fm启动流程:例如以下图: 先进入FMRadio.j ...
- React Native(十五)——RN中的分享功能
终于,终于,可以总结自己使用RN时的分享功能了-- 为什么呢?且听我慢慢道来吧: 从刚开始接触React Native(2017年9月中旬)就着手于分享功能,直到自己参与公司的rn项目开发中,再到现在 ...
- ABP中的模块初始化过程(一)
在总结完整个ABP项目的结构之后,我们就来看一看ABP中这些主要的模块是按照怎样的顺序进行加载的,在加载的过程中我们会一步步分析源代码来进行解释,从而使自己对于整个框架有一个清晰的脉络,在整个Asp. ...
- Android studio中为项目添加模块依赖的过程
https://blog.csdn.net/cheng__lu/article/details/74574582 Android studio中为项目添加模块依赖的过程 1.点击菜单file>p ...
- node 中第三方模块的加载过程原理
node 中第三方模块的加载过程原理 凡是第三方模块都必须通过 npm 来下载 使用的时候就可以通过require('包名') 的方式来进行加载才可以使用 不可能有任何一个第三方包和核心模块的名字是一 ...
- 从源码看 angular/material2 中 dialog模块 的实现
本文将探讨material2中popup弹窗即其Dialog模块的实现. 使用方法 引入弹窗模块 自己准备作为模板的弹窗内容组件 在需要使用的组件内注入 MatDialog 服务 调用 open 方法 ...
- Java EE中的容器和注入分析,历史与未来
Java EE中的容器和注入分析,历史与未来 java中的容器 java中的注入 容器和注入的历史和展望 一.java中的容器 java EE中的注入,使我们定义的对象能够获取对资源和其他依赖项的引用 ...
- tensorflow中slim模块api介绍
tensorflow中slim模块api介绍 翻译 2017年08月29日 20:13:35 http://blog.csdn.net/guvcolie/article/details/77686 ...
- 《React-Native系列》3、RN与native交互之Callback、Promise
接着上一篇<React-Native系列>RN与native交互与数据传递,我们接下来研究另外的两种RN与Native交互的机制 一.Callback机制 首先Calllback是异步的, ...
随机推荐
- word2vec 细节解析1
count.extend(collections.Counter(list1).most_common(2))表示:使用collections.Counter统计list1列表重单词的频数,然后使用m ...
- spring boot 2
服务端验证: // 1.修改实体 @Min(value = 18,message = "必须大于18岁") private int age; // 2.修改add方法 @PostM ...
- Mac 安装Django
首先 我电脑上的python 是 安装Django 是需要通过 pip 来安装的 最新办的python3.4 应该内置了pip 因此这里 需要下载安装pip pip是常用的Python包管理 ...
- jquery中的属性和样式设置
添加属性 $target.attr({"title":"one piece","name":"solgan"}); 为目 ...
- 系统批量运维管理器pexpect详解
一.pexpect介绍 pexpect可以理解成Linux下的expect的Python封装,通过pexpect我们可以实现对ssh.ftp.passwd.telnet等命令进行自动交互,而无需人工干 ...
- Visual Studio2017 设置了vcpkg之后,编译其他程序出问题
博客参考:https://github.com/nodejs/node/issues/23909 错误如下 LNK2005 _SSL_CTX_check_private_key already def ...
- [Java] 获取当前Project所在的路径
String projectPath = System.getProperty ("user.dir").toString()
- 怎样知道自己机器的出口网关IP(即外部IP)
方法一: 在浏览器中输入 : http://www.ip138.com/ 可以查到外部IP地址及地理位置 方法二: Just put in “where is my ip” in google t ...
- docker 构建 spring boot项目
在docker 开始部署springBoot项目 1.在centos7 ~ 创建一个文件夹docker 里面放置 上面的Dockerfile 和 springBoot 打包的项目docker_spri ...
- Thrift结构分析及增加取客户端IP功能实现
目录 目录 1 1. 前言 1 2. 示例Service 1 3. 网络部分类图 2 4. 线程模式 3 4.1. IO线程 3 4.2. 工作线程 4 4.2.1. 工作线程类图 4 4.2.2. ...