找到所有的模块

一般来说,只要在模块中声明 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. go_组合接口

    main函数入口 package main import ( "fmt" "learngo/retriever/mock" "learngo/retr ...

  2. 寻找hash值——把int array看成是一个整数

    QUESTION: Write a class DominoChecker that has a method called addBox(int[]) that takes a box of fiv ...

  3. js 倒计时功能,获取当前时间的年月日,时分秒

    一.实现当前时间到指定截止时间的倒计时功能 <html> <head> <title>TEST</title> </head> <bo ...

  4. [leetcode]273. Integer to English Words 整数转英文单词

    Convert a non-negative integer to its english words representation. Given input is guaranteed to be ...

  5. 用Hash Table(哈希散列表)实现统计文本每个单词重复次数(频率)

    哈希表在查找方面有非常大应用价值,本文记录一下利用哈希散列表来统计文本文件中每个单词出现的重复次数,这个需求当然用NLP技术也很容易实现. 一.基本介绍 1.Hash Key值:将每个单词按照字母组成 ...

  6. java成长之路-开篇

    一,为了生活 从业7年,主要还是运用.net过日子.今儿下决心再次准备学习java并想达到一定高度,也还是想以后能主要用java赚钱过日子.抱歉眼界所到,平均情况下,java平台的收入还是比.net的 ...

  7. 洛谷P1486 [NOI2004]郁闷的出纳员(splay)

    题目描述 OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的工资.这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资 ...

  8. 寻找最大的K个数(下)

    接着昨天的写,里面的代码包含昨天的 #include <iostream> using namespace std; #define N 50 //初始化数组 , , , , , , , ...

  9. AndroidStudio-Unable to save settings Failed to save settings. Please restart Android Studio

    Unable to save settings Failed to save settings. Please restart Android Studio 解决方法: 删除工程的.idea 然后在 ...

  10. Xamarin 技术解析

    Xamarin 是一套基于C#语言的跨平台移动应用开发工具,今年2月份微软宣布收购Xamarin,而后在4月份进行的Build大会上微软宣布将会在各个版本的Visual Studio中免费提供Xama ...