iOS端如何操作
- 创建一个类,然后遵循协议
- 使用RCT_EXPORT_MODULE导出模块
- 使用RCT_EXPORT_METHOD导出异步方法
- RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD导出同步方法
// Test.h文件
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
// 遵循RCTBridgeModule
@interface Test : NSObject <RCTBridgeModule>
@end
// Test.m文件
#import "Test.h"
@implementation Test
/// 导出一个模块,括号内是可选的,若不填,默认为类名
RCT_EXPORT_MODULE(Test);
/// 导出一个普通的异步方法,
RCT_EXPORT_METHOD(test:(NSString *)name) {
NSLog(@"%@",name);
}
/// 导出一个支持Promise的异步方法
RCT_EXPORT_METHOD(testPromise:(NSString *)name
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
resolve(@"success");
}
/// 导出一个同步方法
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, testSync:(NSString *)name) {
return [[NSString alloc]initWithFormat:@"hello %@", name];
}
/// 导出常量供RN使用
- (NSDictionary *)constantsToExport {
return @{@"testConstant": @"constant"};
}
@end
复制代码
RN端如何使用?
- 导入NativeModules模块
- NativeModules.原生导出的模块名.方法名进行调用,如NativeModules.Test.test(“sync”);(方法名默认是第一个冒号之前的内容)
/// 导入模块
import {NativeModules} from 'react-native';
// 调用异步的方法
NativeModules.Test.test("sync");
// 使用await调用支持Promise的方法
let res = await NativeModules.Test.testPromise('promise');
console.log(res)
/// 调用同步的方法
let syncRes = NativeModules.Test.testSync('sync');
console.log(syncRes);
why?
- 为什么RCT_EXPORT_METHOD参数中有了RCTPromiseResolveBlock和RCTPromiseRejectBlock在JS调用的时候就支持Promise了?
- Test类是什么时候实例化的?
- RN端的NativeModules是什么?NativeModules.Test又是什么?
- 总之一个疑问,为什么我在原生导出一下,在RN里就能用js调用,这里面到底经历了什么?
如果你能对上面的问题都清楚,那么下面的内容对你应该没什么帮助。
**
从源码中找寻答案
**
- 先来看看 RCT_EXPORT_MODULE 做了什么?
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+(NSString *)moduleName \
{ \
return @ #js_name; \
} \
+(void)load \
{ \
RCTRegisterModule(self); \
}
根据上面的代码可以看出,RCT_EXPORT_MODULE一共做了两件事,
- 实现了类方法moduleName,返回一个字符串(注:在宏定义中#号代表把后面变量前后添加双引号)
- 在load方法中调用了RCTRegisterModule,这个方法就是把类对象添加到一个全局的数组中
复制代码 - 再来看下RCT_EXPORT_METHOD和RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD做了什么?
由于这两个宏定义嵌套比较多,下面代码都直接显示宏定义完全展开之后的代码
/// 导出一个普通的异步方法,
RCT_EXPORT_METHOD(test:(NSString *)name) {
NSLog(@"%@",name);
}
/// 完全展开之后
-(void)test:(NSString *)name ; {
NSLog(@"%@",name);
}
+(const RCTMethodInfo *)__rct_export__(__LINE__, __COUNTER__这里是当前的行数加上预编译的次数){
static RCTMethodInfo config = {"", "test:(NSString *)name", NO};
return &config;
}
/// 导出一个支持Promise的异步方法
RCT_EXPORT_METHOD(testPromise:(NSString *)name
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
resolve(@"success");
}
/// 完全展开之后
-(void)testPromise:(NSString *)name
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject ; {
resolve(@"success");
}
+(const RCTMethodInfo *)__rct_export__(__LINE__, __COUNTER__这里是当前的行数加上预编译的次数){
static RCTMethodInfo config = {"", "testPromise:(NSString *)name resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject", NO};
return &config;
}
/// 导出一个同步方法
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, testSync:(NSString *)name) {
return [[NSString alloc]initWithFormat:@"hello %@", name];
}
/// 完全展开之后
-(NSString *)testSync:(NSString *)name ; {
return [[NSString alloc]initWithFormat:@"hello %@", name];
}
+(const RCTMethodInfo *)__rct_export__(__LINE__, __COUNTER__这里是当前的行数加上预编译的次数){
static RCTMethodInfo config = {"", "testSync:(NSString *)name", YES};
return &config;
}
通过上面的代码可以看出,
无论RCT_EXPORT_METHOD还是RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD做的事情都一样,都是生成了一个对象方法,方法的名字就是括号内的参数,方法的实现就是宏定义后面跟着的{}里的实现,并且同时生成了一个以`__rct_export__`开头的类方法,里面返回了一个静态变量的结构体的地址,定义如下
typedef struct RCTMethodInfo {
const char *const jsName;
const char *const objcName;
const BOOL isSync;
} RCTMethodInfo;
而RCT_EXPORT_METHOD和RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD的区别是后者生成的方法是带返回值的,而前者固定为void,后者生成的结构体里的信息最后一个字段为YES。
小结:上面一堆宏定义一共做了4件事
- 把当前的类对象添加到一个全局数组内
- 生成了一个moduleName类方法,返回供JS调用的时候的模块名字
- 生成了一堆以__rct_export__开头的类方法,并返回一个RCTMethodInfo结构体
- 生成了一堆真正供js调用的方法
- RCTBridge的初始化过程中创建的全局变量
下面这段代码,会在Bridge初始化的过程中调用,在js执行环境的gloabl上添加了三个全局变量,nativeModuleProxy,nativeFlushQueueImmediate,nativeCallSyncHook后面两个是函数。后面再详细介绍
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(*runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));
runtime_->global().setProperty(
*runtime_,
"nativeFlushQueueImmediate",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
1,
[this](
jsi::Runtime &,
const jsi::Value &,
const jsi::Value *args,
size_t count) {
if (count != 1) {
throw std::invalid_argument(
"nativeFlushQueueImmediate arg count must be 1");
}
callNativeModules(args[0], false);
return Value::undefined();
}));
runtime_->global().setProperty(
*runtime_,
"nativeCallSyncHook",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
1,
[this](
jsi::Runtime &,
const jsi::Value &,
const jsi::Value *args,
size_t count) { return nativeCallSyncHook(args, count); }));
NativeModules是什么?NativeModules.Test又是什么?
先来看NativeModules,NativeModules是从react-native/Libraries/BatchedBridge/NativeModules.js文件中导出的就是上面在原生中创建的全局变量nativeModuleProxy。
NativeModules.Test 最终会调用JSINativeModules::getModule的方法,这个方法主要做了这些。
- 通过模块名字对应的class,然后通过class生成一个数组,数组一共有5个元素,第一个元素是模块名字,第二个元素是需要导出的常量,第三个元素也是一个数组,包含所有导出的方法名字,第四个是所有导出的支持Promise的方法的下标,最后一个是所有导出的同步方法的下标。
- RN是通过遍历所有的类方法,如果发现类方法是__rct_export__开头的,则会调用这个方法,获取其返回的RCTMethodInfo,如果RCTMethodInfo中的jsName为空,则会取其中的objcName的第一个冒号之前的字符串为jsName,如果objcName中包含RCTPromise则会认为这是promise,如果isSync为YES,则会认为这是一个同步方法
- 获取到配置信息之后会调用rn端的全局函数__fbGenNativeModule,也定义在NativeModules.js的87行中。global.__fbGenNativeModule
- = genModule;
- genModule的代码如下,去掉了异常处理的代码
下面代码中的module就是NativeModules.Test这个属性的值,在遍历所有方法的过程,用方法名字为属性名字,genMethod(moduleID, methodID, methodType);的结果为值。genMethod的返回值也是一个函数
function genModule(
config: ?ModuleConfig,
moduleID: number,
){
if (!config) {
return null;
}
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
const module = {};
methods &&
methods.forEach((methodName, methodID) => {
const isPromise =
(promiseMethods && arrayContains(promiseMethods, methodID)) || false;
const isSync =
(syncMethods && arrayContains(syncMethods, methodID)) || false;
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
module[methodName] = genMethod(moduleID, methodID, methodType);
});
Object.assign(module, constants);
if (module.getConstants == null) {
module.getConstants = () => constants || Object.freeze({});
}
return {name: moduleName, module};
}
genMethod的代码如下
genMethod实现是方法类型是则调用BatchedBridge.callNativeSyncHook的方法,如果是异步的方法则调用BatchedBridge.enqueueNativeCall,如果是promise的,则用Promise做一层封装,再调用了BatchedBridge.enqueueNativeCall
function genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = null;
if (type === 'promise') {
fn = function promiseMethodWrapper(...args: Array<mixed>) {
const enqueueingFrameError: ExtendedError = new Error();
return new Promise((resolve, reject) => {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
data => resolve(data),
errorData =>
reject(
updateErrorWithErrorData(
(errorData: $FlowFixMe),
enqueueingFrameError,
),
),
);
});
};
} else {
fn = function nonPromiseMethodWrapper(...args: Array<mixed>) {
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
const hasSuccessCallback = typeof lastArg === 'function';
const hasErrorCallback = typeof secondLastArg === 'function';
hasErrorCallback &&
invariant(
hasSuccessCallback,
'Cannot have a non-function arg after a function arg.',
);
// $FlowFixMe[incompatible-type]
const onSuccess: ?(mixed) => void = hasSuccessCallback ? lastArg : null;
// $FlowFixMe[incompatible-type]
const onFail: ?(mixed) => void = hasErrorCallback ? secondLastArg : null;
const callbackCount = hasSuccessCallback + hasErrorCallback;
const newArgs = args.slice(0, args.length - callbackCount);
if (type === 'sync') {
return BatchedBridge.callNativeSyncHook(
moduleID,
methodID,
newArgs,
onFail,
onSuccess,
);
} else {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
newArgs,
onFail,
onSuccess,
);
}
};
}
fn.type = type;
return fn;
}
- BatchedBridge是MessageQueue的实例就是常说的消息队列,
- BatchedBridge.enqueueNativeCall会调用上面的全局函数nativeFlushQueueImmediate,BatchedBridge.callNativeSyncHook会调用nativeCallSyncHook,至于这两个函数最终是怎么分发到每个具体的方法里的,下篇文章见吧!
写在最后的话
在写这篇文章之前,这些代码已经翻过N遍了,但是真正也起来还是比较乱。写的真累…自己看明白和能写出来真不是一回事
作者:李坤
链接:https://juejin.cn/post/6965082621801955364
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。