以添加CalendarManager
为例
.h
1 | #import <React/RCTBridgeModule.h> |
.m
1 |
|
运行xcode,在下面的地方打上断点
输出configJSON
,只保留CalendarManager
相关的并且json一下就是
1 | { |
这是在debug环境的情况下传的是完整配置信息,而在release环境下这个配置信息就只有module信息,而其他的方法信息都是通过懒加载的形式得到
oc通过方法将jsonstring传给js
1 | [_javaScriptExecutor injectJSONText:configJSON |
js如何接收呢
在NativeModules.js
中
1 | const bridgeConfig = global.__fbBatchedBridgeConfig; |
通过一个global的key__fbBatchedBridgeConfig
将数据给到js
由
1 | let NativeModules : {[moduleName: string]: Object} = {}; |
可以知道NativeModules
本身是一个对象
我们再debugger
1 | (bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => { |
可以过滤出CalendarManager
如下图所示
具体看看这个方法做的事情可以知道每个item都会经过genModule
方法,先来看看
1 | function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, module?: Object} { |
这个又用到了genMethod
方法,好吧先来看看
1 | function genMethod(moduleID: number, methodID: number, type: MethodType) { |
方法分为三种,由oc传给js的时候也有体现的这里我们先直接看aync
情况的,这里做了三件事情
- 定义了一个接收多个参数额方法;
- 在参数列表中取出最后的两个作为js的回调;
- 最终调用的是原生的方法这里又引出了另一个方法
BatchedBridge.enqueueNativeCall
1 | enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) { |
这个方法一共做了以下几件事情:
- 判断如果后面两个参数有回调的话就会为每个回调生成一个唯一的ID这里叫做
_callbackID
,并将js方法装进一个_callbacks
的数组中; 这里有个
_queue
对象他的结构如下1
2
3_queue: [Array<number>, Array<number>, Array<any>, number];
在js中分别以`MODULE_IDS` `METHOD_IDS` 和 `PARAMS`来表示数组的第0、1、2号元素,之所以这样命名是达到为了见名之意的效果完成了第一步的
_callbackID
的映射,在分别吧对应的模块idmoduleID
、方法idmethodID
和参数params
分别装进_queue
对应的数组中1
2
3this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
this._queue[PARAMS].push(params);回调原生的
global.nativeFlushQueueImmediate
方法,对应原生的
1
2
3
4
5
6
7
8
9
10
context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid || !calls) {
return;
}
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeFlushQueueImmediate", nil);
[strongSelf->_bridge handleBuffer:calls batchEnded:NO];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call");
};
值得注意的是这里只是做方法的申明,并没有调用。
那我们在回到最开始的genModule
方法,也就是说经过genModule
方法以后以我们的CalendarManager
来说的话也就是返回一个下面形式的对象
1 | { |
再回到一开始的方法
1 | if (info.module) { |
从而经过一大圈的变换最后得到的结果就是返回一个NativeModules
对象,还是以CalendarManager
为例,得到的NativeModules
的对象如下
1 | CalendarManager: |
我们自定义的CalendarManager
作为了NativeModules
的一个属性,CalendarManager
所对应的方法列表成了CalendarManager
属性
这里只是用CalendarManager
为例子来讲的,实际上最后NativeModules
是有很多的类似CalendarManager
的属性
也就是说每一个原生模块都对应NativeModules
一个属性
到这里也就说完了原生创建一个模块到js的演变过程,我们来总结一下,当我们要创建一个原生模块的时候需要做些什么;
- 要继承
这个协议; 要在.m中加入RCT_EXPORT_MODULE();还记得之前那个发给js的那个原生模块的jsonString吧这个目的其实是要将当前模块加入配置表中也就是下面的这个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14void 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];
}对于要暴露给js的oc方法需要使用
RCT_EXPORT_METHOD
这个宏1
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
展开以后就是下面这个样子
1
2
3
4
5
+ (NSArray<NSString *> *)__rct_export__300 {
return @[@"",
@"addEvent:(NSString *)name location:(NSString *)location "];
}
- (void)addEvent:(NSString *)name location:(NSString *)location;
到后面要查找方法列表的时候就是通过`__rct_export__`前缀来查找的,查找的方法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- (NSArray<id<RCTBridgeMethod>> *)methods
{
if (!_methods) {
NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new];
if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
[moduleMethods addObjectsFromArray:[self.instance methodsToExport]];
}
unsigned int methodCount;
Class cls = _moduleClass;
while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
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<NSString *> *entries =
((NSArray<NSString *> *(*)(id, SEL))imp)(_moduleClass, selector);
id<RCTBridgeMethod> moduleMethod =
[[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
JSMethodName:entries[0]
moduleClass:_moduleClass];
[moduleMethods addObject:moduleMethod];
}
}
free(methods);
cls = class_getSuperclass(cls);
}
_methods = [moduleMethods copy];
}
return _methods;
}
这个方法做了两个事情,一个是查找已`__rct_export__`开头的方法,一个是以`__rct_export__`方法开头的第一个参数为js方法的名称。
由3可知以这个宏
RCT_EXPORT_METHOD
的话默认给js的方法名称是参数的第一位,可以知道都是空的,可是我们最后是需要给js一个方法一个名称的这里oc做了默认处理了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- (NSString *)JSMethodName
{
NSString *methodName = _JSMethodName;
if (methodName.length == 0) {
methodName = _methodSignature;
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.location != NSNotFound) {
methodName = [methodName substringToIndex:colonRange.location];
}
methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
RCTAssert(methodName.length, @"%@ is not a valid JS function name, please"
" supply an alternative using RCT_REMAP_METHOD()", _methodSignature);
}
return methodName;
}也就是如果
JSMethodName
的长度为空的话就会截取第二个参数的:
以前的字符串作为方法,也就是
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
对应addEvent
,RCT_EXPORT_METHOD(fangshufeng:(NSString *)name testTwo:(NSString *)location)
对应fangshufeng
,那这样的话问题就来了,万一还有个这样的方法RCT_EXPORT_METHOD(fangshufeng:(NSString *)name)
按照默认的规则岂不是就和RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
冲突了吗,rn为了避免这种情况,我们可以使用这个宏RCT_REMAP_METHOD
,也就是这种用法1
2
3
4RCT_REMAP_METHOD(fangshufengJsName,
addEvent:(NSString *)name testTwo:(NSString *)location hh:(NSString*)jj) {
NSLog(@"location4-----%@",location);
}这样的话之前那个数组的参数第一个值就有值了对应
fangshufengJsName
在研究js源码的时候我们发现,js对参数的解析是有要注意的地方的,如果原生想要使用js的回调的话只能放在参数的后两位
1
2
3
4
5RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location action:(RCTResponseSenderBlock)action)
{
// Date is ready to use!
NSLog(@"location1-----%@",location);
}这样的话就是错的
1
2
3
4
5RCT_EXPORT_METHOD(addEvent:(NSString *)name action:(RCTResponseSenderBlock)action location:(NSString *)location)
{
// Date is ready to use!
NSLog(@"location1-----%@",location);
}
接下来将要讲的是当自定义的模块方法被调用时数据是如何流动的。
首先我们先把CalendarManager
用起来
1 | import React,{Component} from 'react'; |
运行的结果
点击fading in
也就是调用fangshufeng
方法,之前我们说过enqueueNativeCall
方法只是做方法申明,这个时候就是调用之前定义的方法,在我的这个工程里面可以debug到,fangshufeng
这个方法的moduleID
是1
,methodID
是1
然后debug到enqueueNativeCall
中
可以看到最终传给oc的queue
是什么
如果只看CalendarManager
的话当你点击Fading in
的时候传给oc的queue就是
1 | [[1],[1],[['name','ggg']]] |
然后通过nativeFlushQueueImmediate
方法给到oc,会进到下面的这个方法
1 | - (void)handleBuffer:(NSArray *)buffer |
在需要的解释的地方做了个标记
- 由于下面要用到
object->object
所以这里使用map
也就是NSMapTable
来保存数据; 这个地方可能有点绕,如果对数据结构不清楚的话比较难以理解,还是以
CalendarManager
为例子,先来假设其他的数据如下所示:
假设js传给oc的queue是这个样子的1
2
3
4
5[
[38,19,41,41,1], // 最后一个1表示`CalendarManager`模块
[19,1,4,3,1], // 最后一个1表示addEvent: location: 方法
[[...],[...],[...],[...],['name','ggg']] //['name','ggg']表示方面方法的入参
]
我们通过断点来跟踪一下数据
`modules`
1
2
3
4
5
6
7
<__NSArrayI 0x7b048e30>(
38,
19,
41,
41,
1
)
`buckets`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
po buckets
NSMapTable {
[10] <OS_dispatch_queue: com.facebook.react.CalendarManagerQueue[0x7b652310]> -> {(
4
)}
[46] <OS_dispatch_queue: com.facebook.react.ShadowQueue[0x7b19cb90]> -> {(
0,
2,
3
)}
[47] <null> -> {(
1
)}
}
1
2
3
4
5
6
7
8
po methodIDs
<__NSArrayI 0x7b048da0>(
19,
1,
4,
3,
1
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
po paramsArrays
<__NSArrayI 0x7ee5f7c0>(
<__NSArray0 0x7a648bd0>(
)
,
<__NSSingleObjectArrayI 0x7b04c8d0>(
47
)
,
<__NSSingleObjectArrayI 0x7b04daf0>(
3
)
,
<__NSArrayI 0x7ee55570>(
4,
3,
{
frames = (
0,
"0.008888888888888889",
"0.03555555555555556",
"0.08000000000000002",
"0.1422222222222222",
"0.2222222222222223",
"0.3200000000000001",
"0.4355555555555557",
"0.5644444444444444",
"0.6799999999999999",
"0.7777777777777777",
"0.8577777777777778",
"0.9199999999999999",
"0.9644444444444443",
"0.9911111111111111",
1,
1
);
iterations = 1;
toValue = 1;
type = frames;
},
13
)
,
<__NSArrayI 0x7b2630e0>(
name,
ggg
)
做的事情就是:
遍历模块的数组 -> 在`_moduleDataByID`取出`moduleData` -> 以`moduleData`中的串行队列作为key set作为value,而set中装着的对应的索引,由于module、method、Params都是索引值是一一对应的,这个索引值即是当前module在modules数组的索引值,也是method、Params的索引值。
上面已经给出了bucket的具体内容,循环后也就是调用下面的方法
1
2
3
4
5
6
7
8
9
10
11[self callNativeModule:[moduleIDs[index] integerValue]
method:[methodIDs[index] integerValue]
params:paramsArrays[index]];
// 具体就是
[self callNativeModule:1 //可以知道这个1就是`CalendarManager`
method:1 // 这个1就是表示addEvent: location: 方法
params:['name','ggg'] //表示方面方法的入参
];
//具体上面怎么解析的下面会做分析
oc拿到数据得到模块名称、方法名称、参数,然后执行