找到所有的模块

一般来说,只要在模块中声明 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. 查看http的并发请求数及其TCP连接状态

    统计80端口的连接数据 netstat -nat | grep -i "80" | wc -l 统计httpd协议连接数 ps -ef | grep httpd | wc -l 统 ...

  2. HttpClient详解

    HttpClient:是一个接口 首先需要先创建一个DefaultHttpClient的实例 HttpClient httpClient=new DefaultHttpClient(); 发送GET请 ...

  3. 全球数据库-->基金/管理产品-->基金分析/新闻/报告

    加拿大共同基金 澳大利亚投资信托 美国ETF 美国共同基金 英国投资信托基金 名称 分析师名称 分析日期 晨星分析师评级 晨星简报

  4. open /etc/docker/certs.d/registry.access.redhat.com/redhat-ca.crt: no such file or directory 解决方案

    方法一.  yum安装 yum install *rhsm* 方法二 (我是用这方法解决的) 执行命令: ①   wget http://mirror.centos.org/centos/7/os/x ...

  5. __lll_mutex_lock_wait的错误原因

    1. x86_64栈(glib 2.4): free时: (gdb) bt #0  0x00002b9405ea1c38 in __lll_mutex_lock_wait () from /lib64 ...

  6. swift 学习之 UIAlertViewController

    // //  PushViewController.swift //  tab // //  Created by su on 15/12/7. //  Copyright © 2015年 tian. ...

  7. 关于wcf配置未启动net.tcp监控导致无法访问wcf

    在服务里面启动NetTcpActivator和NetTcpPortSharing服务

  8. awk基础02-变量-分隔符-数组

        对任意一门语言都会有变量,在awk中变量分为内置变量和自定义变量. 内置变量:就是预先在awk中定义好的,用户可以直接使用 自定义变量:这种变量为用户自己定义的变量,需要先定义后再使用. 内置 ...

  9. 团体程序设计天梯赛L2-024 部落 2017-04-18 11:31 274人阅读 评论(0) 收藏

    L2-024. 部落 时间限制 120 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 在一个社区里,每个人都有自己的小圈子,还可能同时属于很多不 ...

  10. Linux的进程/线程间通信方式总结

    Linux系统中的进程间通信方式主要以下几种: 同一主机上的进程通信方式 * UNIX进程间通信方式: 包括管道(PIPE), 有名管道(FIFO), 和信号(Signal) * System V进程 ...