ReactNative iOS 交互
if(mod&&mod.isInitialized){return mod.module.exports;}好,我们抓紧回来,在上段代码中当BatchedBridge module创建时,通过这句 Object.defineProperty(global,‘__fbBatchedBridge’,{value:BatchedBridge}); 把自己定义到JSContext的全局变量
if(mod&&mod.isInitialized){
return mod.module.exports;}
好,我们抓紧回来,在上段代码中当BatchedBridge module创建时,通过这句 Object.defineProperty(global,‘__fbBatchedBridge’,{value:BatchedBridge}); 把自己定义到JSContext的全局变量上。所以在Native代码中可以通过 JSContext[@“__fbBatchedBridge”]获取到,
从代码中也可以看到BatchedBridge 是JS类MessageQueue的实例,并且它导出的时候并没有导出构造函数MessageQueue,而是导出的实例BatchedBridge,所以它是React Native JS引擎中全局唯一的。它也是Natvie和JS互通的关键桥梁。
__fbBatchedBridge.callFunctionReturnFlushedQueue(“AppRegistr”,“runApplication”,[“MGReactNative”,{“rootTag”:1,“initialProps”:{}}])
我们继续看MessageQueue 类的callFunctionReturnFlushedQueue 函数,它最终调用到__callFunction(module, method, args)函数
__callFunction(module, method, args) {
var moduleMethods = this._callableModules[module];
if (!moduleMethods) {
moduleMethods = require(module);
}
moduleMethods[method].apply(moduleMethods, args);
}
看起来__callFunction就是最终的分发函数了,首先它从this._callableModules中找到模块对象,如果它还没有加载,就动态加载它(require),如果找到就执行最终的JS函数。
自己开发的JS模块如果暴露给Native调用
先看下AppRegistry是如何暴露给Natvie的
__d(‘AppRegistry’,function(global, require, module, exports) { ‘use strict’; var BatchedBridge=require(‘BatchedBridge’); var ReactNative=require(‘ReactNative’); var AppRegistry={ runApplication:function(appKey,appParameters){ runnables[appKey].run(appParameters); }, } BatchedBridge.registerCallableModule( ‘AppRegistry’, AppRegistry); module.exports=AppRegistry; });
有前面的讲解,现在看这个应该不态费劲了,可以看到AppRegistry模块工厂函数中,执行了 BatchedBridge.registerCallableModule(‘AppRegistry’,AppRegistry);,把自己注册到BatchedBridge的CallableModule中,所以在上一节中,__callFunction才能在_callableModules找到AppRegistry实例,才能调用其runApplication函数。自己写的模块代码可以用React Native这种方式暴露给Natvie调用,和直接暴露的区别是,符合React Natvie的模块化原则,另外一个直观的好处是你的模块可以是懒加载的,并且不会污染全局空间。
目前终于把从N-JS的整个路径跑通了,我们梳理下整个流程看看。
[RCTBatchedBridge enqueueJSCall:@“AppRegistry.runApplication” args:[“MGReactNative”,{“rootTag”:1,“initialProps”:{}}]];
RCTJavaScriptContext callFunctionOnModule:@“AppRegistr”
method:@"runApplication"
arguments:["MGReactNative",{"rootTag":1,"initialProps":{}}]
callback:(RCTJavaScriptCallback)onComplete
//main.js
__fbBatchedBridge.callFunctionReturnFlushedQueue(“AppRegistr”,“runApplication”,[“MGReactNative”,{“rootTag”:1,“initialProps”:{}}])
//main.js
BatchedBridge.__callFunction(“AppRegistr”,“runApplication”,[“MGReactNative”,{“rootTag”:1,“initialProps”:{}}])
//main.js
var moduleMethods = BatchedBridge._callableModules[module];
if (!moduleMethods) {
moduleMethods = require(module);
}
moduleMethods[method].apply(moduleMethods, args);
JS调用Native (JS->Native)
接下来我们看看从JS如何调用Native,换句话说Native如何开放API给JS
我们以弹Alert框的接口为例,这是Native的OC代码,导出RCTAlertManager类的alertWithArgs:(NSDictionary *)args
callback:(RCTResponseSenderBlock)callback)方法
@interface RCTAlertManager() : NSObject <RCTBridgeModule, RCTInvalidating>
…
@end
@implementation RCTAlertManager
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args
callback:(RCTResponseSenderBlock)callback)
{
…
}
#end
要把OC类或实例的函数导出给JS用,需实现以下三个步骤
-
OC类实现RCTBridgeModule协议
-
在.m的类实现中加入RCT_EXPORT_MODULE(),帮助你实现RCTBridgeModule协议
-
要导出的函数用RCT_EXPORT_METHOD()宏括起来,不用这个宏,不会导出任何函数
现在从JS里可以这样调用这个方法:
var RCTAlertManager=require(‘react-native’).NativeModules.AlertManager; RCTAlertManager.alertWithArgs({message:‘JS->Native Call’,buttons:[{k1:‘button1’},{k2:‘button1’}]},function(id,v) {console.log(‘RCTAlertManager.alertWithArgs() id:’ + id +’ v:’ + v)});
执行之后的效果,弹出一个Alert

alert.png
对于详细的如何导出函数推荐阅读[Native Modules](()
我们今天的目的不是和女神喝茶聊天,是深入女神内心,是内心咳咳。来看看今天的重点
动态导出Native API,延迟加载Native 模块
在JS中可以直接使用RCTAlertManager.alertWithArgs来调用,说明JS中已经定义了和OC对象相对应的JS对象,我们从导出一个Native类开始,完整跟踪下这个过程。
生成Native API 配置表
RCTAlertManager类实现了RCTBridgeModule协议,并且在类的实现里包含了RCT_EXPORT_MODULE() 宏
@protocol RCTBridgeModule
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
-
(NSString *)moduleName { return @#js_name; } \
-
(void)load { RCTRegisterModule(self); }
// Implemented by RCT_EXPORT_MODULE
- (NSString *)moduleName;
@optional
在OC里,一个类所在文件被引用时,系统会调用其+(void)load函数,当RCTAlertManager所在文件被引用时,系统调用load 函数,函数里简单的调用RCTRegisterModule(self) 把自己注册到一个全局数组RCTModuleClasses,这样系统中导出的类都会自动注册到这个全局变量数组里(so easy)。
在JS中有一个BatchedBridge用来和Native通讯,在Natvie中也有一个RCTBatchedBridge类,它封装了JSContext即JS引擎
在RCTBatchedBridge start 函数中,做了5件事
- jsbundle文件的下载或本地读取(异步)
- 初始化导出给JS用的Native模块
- 初始化JS引擎
- 生成配置表,并注入到JS引擎中,
- 执行jsbundle文件。
//伪代码
- (void)start
{
//1 jsbundle文件的下载或本地读取(异步)
NSData *sourceCode;
[self loadSource:^(NSError *error, NSData *source) {sourceCode = source}];
//2 初始化导出给JS用的Native模块
[self initModules];
//3 初始化JS引擎
[self setUpExecutor];
//4 生成Native模块配置表 把配置表注入到JS引擎中
NSSting* config = [self moduleConfig];
[self injectJSONConfiguration:config onComplete:^(NSError *error) {});
//5 最后执行jsbundle
[self executeSourceCode:sourceCode];
}
现在我们最关心第二步初始化Native模块 initModules 和moduleConfig 到底是什么
//伪代码
- (void)initModules
{
//遍历上节讲到的RCTGetModuleClasses全局数组,用导出模块的类或者实例创建RCTModuleData
for (Class moduleClass in RCTGetModuleClasses())
{
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
//这里一个很有意思的地方,如果导出的类或其任何父类重写了init方法,或者类中有setBridge方法
//则React Native假设开发者期望这个导出模块在Bridge第一次初始化时实例化,否则怎么样,大家想想
if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod ||
[moduleClass instancesRespondToSelector:setBridgeSelector]) {
module = [moduleClass new];
}
// 创建RCTModuleData
RCTModuleData *moduleData;
if (module) {
moduleData = [[RCTModuleData alloc] initWithModuleInstance:module];
} else {
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self];
}
//保存到数组中,数组index就是这个模块的索引
[_moduleDataByID addObject:moduleData];
}
}
initModules里根据是否重写init或添加了setBridge来决定是不是要马上实例化RCTGetModuleClasses里的导出类,然后用实例或类创建RCTModuleData,缓存到本地,以便JS调用时查询。
再来看第四步导出的 NSSting* config = [self moduleConfig] 是什么内容
{“remoteModuleConfig”:
[[“RCTStatusBarManager”],
[“RCTSourceCode”],
[“RCTAlertManager”],
[“RCTExceptionsManager”],
[“RCTDevMenu”],
[“RCTKeyboardObserver”],
[“RCTAsyncLocalStorage”],
.
.
.
]}
它仅仅是一个类名数组。
注入配置表到JS引擎,并创建对应的JS对象
生产配置表后,通过下面的方法把这个类名数组注入到JSContext,赋值给JS全局变量__fbBatchedBridgeConfig
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@“__fbBatchedBridgeConfig”
callback:onComplete];
在JS端,当有人引用了BatchedBridge var BatchedBridge=require('BatchedBridge');,其工厂函数会通过 __fbBatchedBridgeConfig配置表创建MessageQueue的实例BatchedBridge
var MessageQueue=require(‘MessageQueue’);
var BatchedBridge=new MessageQueue(
__fbBatchedBridgeConfig.remoteModuleConfig,
__fbBatchedBridgeConfig.localModulesConfig);
我们看看MessageQueue的构造函数,构造函数里为每个导出类创建了一个对应的module对象,因为此时config里只有一个导出类的名字,所以这里只为这个对象增加了一个成员变量 module.moduleID,并把module保存到this.RemoteModules数组里
_genModule(config, moduleID) {
let module = {};
if (!constants && !methods && !asyncMethods) {
module.moduleID = moduleID;
}
this.RemoteModules[moduleName] = module;
}
接着我们顺藤摸瓜看看那里使用的BatchedBridge.RemoteModules
NativeModules模块
NativeModules在初始化时,用BatchedBridge.RemoteModules保存的类名列表,为每个JS对象增加了函数等属性
__d(‘NativeModules’,function(global, require, module, exports) { ‘use strict’; var RemoteModules=require(‘BatchedBridge’).RemoteModules; var NativeModules={}; //遍历NativeModules中导出类名 Object.keys(RemoteModules).forEach(function(moduleName){ //把类名定义为NativeModules的一个属性,比如AlertManager类,定义只有就可以用NativeModules.AlertManager 访问 Object.defineProperty(NativeModules,moduleName,{ //这个属性(AlertManager)是可以遍历的,当然属性也是个对象里面有属性和函数 enumerable:true, //属性都有get和set函数,当调用访问这个属性时,会调用get函数 NativeModules.AlertManager get:function(){ var module=RemoteModules[moduleName]; if(module&&typeof module.moduleID===‘number’&&global.nativeRequireModuleConfig){ //调用Native提供的全局函数nativeRequireModuleConfig查询AlertManager 导出的常量和函数 var json=global.nativeRequireModuleConfig(moduleName); module=config&&BatchedBridge.processModuleConfig(JSON.parse(json),module.moduleID); RemoteModules[moduleName]=module; } return module; } }); }); module.exports=NativeModules; });
React Native 把所有的Native导出类定义在一个NativeModules模块里,所以使用Natvie接口时也可以直接这样拿到对应的JS对象
var RCTAlertManager=require('NativeModules').AlertManager;
代码里我加了注释
思考一个问题,为什么React Natvie搞的那么麻烦,为什么不在上一个步骤里(MessageQueue的构造函数)里就创建出完整的JS对象。
没错,就是模块的懒加载,虽然Native导出了Alert接口,在JS引擎初始化后,JS里只存在一个名字为AlertManager的空对象

906C7A90-0A85-4FD6-B433-39CE041D4445.png
当调用了RCTAlertManager.alertWithArgs({message:‘JS->Native Call’,buttons:[{k1:‘button1’}时,才会调用AlertManager 的get函数到Native里查询导出的常量和函数,并定义到AlertManager中。

7RGT1@Z}N19_9{KQ~P_SDFE.jpg
Native模块对应的JS对象中函数是如何调用到Native
RCTAlertManager.alertWithArgs 这个函数是如何调用到Native里的呢,在BatchedBridge.processModuleConfig函数中,用_genMethod创建了一个闭包fn为每个函数赋值,这个函数转调self.__nativeCall(module, method, args, onFail, onSucc); 我们调用RCTAlertManager.alertWithArgs函数,其实都是调用的这个fn闭包。
_genMethod(module, method, type) {
fn = function(…args) {
return self.__nativeCall(module, method, args, onFail, onSucc);
};
return fn;
}
__nativeCall,好熟悉的名字,
__nativeCall(module, method, params, onFail, onSucc) {
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
global.nativeFlushQueueImmediate(this._queue);
this._queue = [[],[],[]];
this._lastFlush = now;
}
global.nativeFlushQueueImmediate 是Native提供的接口,__nativeCall把需要调用的module,method,params都塞到队列里,然后传递到Native,
我们在回到Native 找到上文提到的两个关键接口,Native模块查询接口:global.nativeRequireModuleConfig和调用接口global.nativeFlushQueueImmediate,他们是在JS引擎(JSContext)初始化时,定义到全局变量的。
//RCTContextExecutor setUP
//简化过的代码
- (void)setUp
{
…
self->_context.context[@“nativeRequireModuleConfig”] = ^NSString *(NSString *moduleName) {
NSArray *config = [weakBridge configForModuleName:moduleName];
return RCTJSONStringify(config, NULL);
};
self->_context.context[@“nativeFlushQueueImmediate”] = ^(NSArray<NSArray *> *calls){
[weakBridge handleBuffer:calls batchEnded:NO];
};
…
}
[weakBridge handleBuffer:calls batchEnded:NO]; 经过一系列传递,调用到_handleRequestNumber 中,用moduleID找到RCTModuleData,再用methodID 找到id<RCTBridgeMethod> method 然后在moduleData.instance实例中执行
- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSUInteger)moduleID
methodID:(NSUInteger)methodID
params:(NSArray *)params
{
RCTModuleData *moduleData = _moduleDataByID[moduleID];
id method = moduleData.methods[methodID];
[method invokeWithBridge:self module:moduleData.instance arguments:params];
}
这里有必要再强调一次moduleData.instance 这个地方。
- (id)instance
{
if (!_instance) {
_instance = [_moduleClass new];
…
}
return _instance;
}
还记的前面BatchedBridge 初始化时的initModules吗
//这里一个很有意思的地方,如果导出的类或其任何父类重写了init方法,或者类中有setBridge方法
//则React Native假设开发者期望这个导出模块在Bridge第一次初始化时实例化,否则怎么样,大家想想
if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod ||
[moduleClass instancesRespondToSelector:setBridgeSelector]) {
module = [moduleClass new];
}
否则就是在用户真正调用时,在moduleData.instance里实例化,React Native已经懒到骨髓了。
RCTModuleData中每个函数的封装 RCTModuleMethod里还有一个优化点,JS传递到Native的参数需要进行响应的转换,RCTModuleMethod在调用函数只前,先预解析一下,创建每个参数转换的block,缓存起来,这样调用时,就直接使用对应函数指针进行参数转换了,大要详细了解可以看 - (void)processMethodSignature函数。
《大厂前端面试题解析+Web核心总结学习笔记+企业项目实战源码+最新高清讲解视频》无偿开源 徽信搜索公众号【编程进阶路】
回调函数
前面我们为了直观,忽略了回调函数,alertWithArgs的第二个参数是一个JS回调函数,用来指示用户点击了哪个button,并打印出一行日志。
RCTAlertManager.alertWithArgs({message:‘JS->Native Call’,buttons:[{k1:‘button1’},{k2:‘button1’}]},function(id,v)
{console.log(‘RCTAlertManager.alertWithArgs() id:’ + id +’ v:’ + v)});
回调函数的调用和直接从Native调用JS是差不多的,再回头看看__nativeCall 函数我们忽略的部分
__nativeCall(module, method, params, onFail, onSucc) {
//Native接口最多支持两个回调
if (onFail || onSucc) {
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
}
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
编程进阶路】**
回调函数
前面我们为了直观,忽略了回调函数,alertWithArgs的第二个参数是一个JS回调函数,用来指示用户点击了哪个button,并打印出一行日志。
RCTAlertManager.alertWithArgs({message:‘JS->Native Call’,buttons:[{k1:‘button1’},{k2:‘button1’}]},function(id,v)
{console.log(‘RCTAlertManager.alertWithArgs() id:’ + id +’ v:’ + v)});
回调函数的调用和直接从Native调用JS是差不多的,再回头看看__nativeCall 函数我们忽略的部分
__nativeCall(module, method, params, onFail, onSucc) {
//Native接口最多支持两个回调
if (onFail || onSucc) {
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
}
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
更多推荐



所有评论(0)