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是异步的, ...
随机推荐
- interrupt()方法的简单理解
interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程.这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态.更确切的说,如果线程被Objec ...
- Python全栈工程师(Python3 所有基础内容 0-0)
ParisGabriel 每天坚持手写 一天一篇 决定坚持几年 为了梦想为了信仰 开局一张图 Python一个月的基础语法 基本就到这咯 接下来是数据 ...
- jQuery中mouseover和mouseout冒泡产生闪烁问题
问题:在jQuery中,对元素绑定mouseover和mouseout事件时,每次移入移出该元素和子元素时,都会触发事件,从而会出现闪动的现象. 原因:浏览器的冒泡行为. 解决方案: 使用mousee ...
- cin和gitchar的区别
cin是iostream(输入输出类) 类下的istream(输入类)类的对象,作用是顺序输入字符串.cin.get()是cin的方法.cin.get()是C++面向对象的操作,getchar()是C ...
- 通过HttpClient请求webService
通过HttpClient请求webService 由于服务端是用webService开发的,android要调用webService服务获取数据,这里采用的是通过HttpClient发送post请求, ...
- 基于jCOM搭建Java-微软信息桥梁(下)
第一部分析了BEA提供的Java/COM互操作解决方案—jCOM的实现原理:本文是第二部分,比较全面地分析了Weblogic Server的jCOM实现技术之后,通过一个具体实例来说明了jCOM的具体 ...
- linux系统上查看硬件信息
一:查看CPU more /proc/cpuinfo | grep "model name" grep "model name" /proc/cpuinfo 如 ...
- [转载]MVC、MVP以及Model2(下)
通过采用MVC模式,我们可以将可视化UI元素的呈现.UI处理逻辑和业务逻辑分别定义在View.Controller和Model中,但是对于三者之间的交互,MVC并没有进行严格的限制.最为典型的就是允许 ...
- div自适应宽度
对于div自适应宽度,网上的说法基本上都是将要自适应宽度的div放在其它固定宽度的最后,不指定其float属性或将float属性指定为none,比如三栏布局居中的一栏为自适应宽度,就可以这样来定义三栏 ...
- Angularjs 分页控件
实现效果: 实现步骤: 1.分页页面:page.html,页面多余样式,需要自己去除. <div class="row" ng-show="conf.totalIt ...