reactNative如何調(diào)用原生(iOS)方法
- iOS端如何操作
- 創(chuàng)建一個類,然后遵循<RCTBridgeModule>協(xié)議
- 使用RCT_EXPORT_MODULE導(dǎo)出模塊
- 使用RCT_EXPORT_METHOD導(dǎo)出異步方法
- RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD導(dǎo)出同步方法
// Test.h文件 #import <Foundation/Foundation.h> #import <React/RCTBridgeModule.h> // 遵循RCTBridgeModule @interface Test : NSObject <RCTBridgeModule> @end // Test.m文件 #import "Test.h" @implementation Test /// 導(dǎo)出一個模塊庶香,括號內(nèi)是可選的神年,若不填挺份,默認(rèn)為類名 RCT_EXPORT_MODULE(Test); /// 導(dǎo)出一個普通的異步方法, RCT_EXPORT_METHOD(test:(NSString *)name) { NSLog(@"%@",name); } /// 導(dǎo)出一個支持Promise的異步方法 RCT_EXPORT_METHOD(testPromise:(NSString *)name resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@"success"); } /// 導(dǎo)出一個同步方法 RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, testSync:(NSString *)name) { return [[NSString alloc]initWithFormat:@"hello %@", name]; } /// 導(dǎo)出常量供RN使用 - (NSDictionary *)constantsToExport { return @{@"testConstant": @"constant"}; } @end
- RN端如何使用?
- 導(dǎo)入NativeModules模塊
- NativeModules.原生導(dǎo)出的模塊名.方法名進(jìn)行調(diào)用蛔外,如NativeModules.Test.test("sync");(方法名默認(rèn)是第一個冒號之前的內(nèi)容)
/// 導(dǎo)入模塊 import {NativeModules} from 'react-native'; // 調(diào)用異步的方法 NativeModules.Test.test("sync"); // 使用await調(diào)用支持Promise的方法 let res = await NativeModules.Test.testPromise('promise'); console.log(res) /// 調(diào)用同步的方法 let syncRes = NativeModules.Test.testSync('sync'); console.log(syncRes);
- why雷蹂?
- 為什么RCT_EXPORT_METHOD參數(shù)中有了RCTPromiseResolveBlock和RCTPromiseRejectBlock在JS調(diào)用的時候就支持Promise了伟端?
- Test類是什么時候?qū)嵗模?/li>
- RN端的NativeModules是什么?NativeModules.Test又是什么?
- 總之一個疑問匪煌,為什么我在原生導(dǎo)出一下荔泳,在RN里就能用js調(diào)用蕉饼,這里面到底經(jīng)歷了什么?
如果你能對上面的問題都清楚玛歌,那么下面的內(nèi)容對你應(yīng)該沒什么幫助昧港。
從源碼中找尋答案
1. 先來看看 RCT_EXPORT_MODULE 做了什么?
```c
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+(NSString *)moduleName \
{ \
return @ #js_name; \
} \
+(void)load \
{ \
RCTRegisterModule(self); \
}
```
根據(jù)上面的代碼可以看出支子,RCT_EXPORT_MODULE一共做了兩件事创肥,
1. 實現(xiàn)了類方法moduleName,返回一個字符串(注:在宏定義中#號代表把后面變量前后添加雙引號)
2. 在load方法中調(diào)用了RCTRegisterModule值朋,這個方法就是把類對象添加到一個全局的數(shù)組中
2. 再來看下RCT_EXPORT_METHOD和RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD做了什么?
由于這兩個宏定義嵌套比較多叹侄,下面代碼都直接顯示宏定義完全展開之后的代碼
```c
/// 導(dǎo)出一個普通的異步方法,
RCT_EXPORT_METHOD(test:(NSString *)name) {
NSLog(@"%@",name);
}
/// 完全展開之后
-(void)test:(NSString *)name ; {
NSLog(@"%@",name);
}
+(const RCTMethodInfo *)__rct_export__(__LINE__, __COUNTER__這里是當(dāng)前的行數(shù)加上預(yù)編譯的次數(shù)){
static RCTMethodInfo config = {"", "test:(NSString *)name", NO};
return &config;
}
/// 導(dǎo)出一個支持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__這里是當(dāng)前的行數(shù)加上預(yù)編譯的次數(shù)){
static RCTMethodInfo config = {"", "testPromise:(NSString *)name resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject", NO};
return &config;
}
/// 導(dǎo)出一個同步方法
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__這里是當(dāng)前的行數(shù)加上預(yù)編譯的次數(shù)){
static RCTMethodInfo config = {"", "testSync:(NSString *)name", YES};
return &config;
}
```
通過上面的代碼可以看出昨登,
無論RCT_EXPORT_METHOD還是RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD做的事情都一樣趾代,都是生成了一個對象方法,方法的名字就是括號內(nèi)的參數(shù)丰辣,方法的實現(xiàn)就是宏定義后面跟著的{}里的實現(xiàn)撒强,并且同時生成了一個以`__rct_export__`開頭的類方法,里面返回了一個靜態(tài)變量的結(jié)構(gòu)體的地址笙什,定義如下
```
typedef struct RCTMethodInfo {
const char *const jsName;
const char *const objcName;
const BOOL isSync;
} RCTMethodInfo;
```
而RCT_EXPORT_METHOD和RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD的區(qū)別是后者生成的方法是帶返回值的飘哨,而前者固定為void,后者生成的結(jié)構(gòu)體里的信息最后一個字段為YES琐凭。
小結(jié):上面一堆宏定義一共做了4件事
- 把當(dāng)前的類對象添加到一個全局?jǐn)?shù)組內(nèi)
- 生成了一個moduleName類方法芽隆,返回供JS調(diào)用的時候的模塊名字
- 生成了一堆以
__rct_export__
開頭的類方法,并返回一個RCTMethodInfo結(jié)構(gòu)體 - 生成了一堆真正供js調(diào)用的方法
3. RCTBridge的初始化過程中創(chuàng)建的全局變量
下面這段代碼统屈,會在Bridge初始化的過程中調(diào)用也祠,在js執(zhí)行環(huán)境的gloabl上添加了三個全局變量昙衅,nativeModuleProxy腺办,nativeFlushQueueImmediate代态,nativeCallSyncHook后面兩個是函數(shù)。后面再詳細(xì)介紹
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); }));
4. NativeModules是什么惩淳?NativeModules.Test又是什么?
先來看NativeModules蕉毯,NativeModules是從react-native/Libraries/BatchedBridge/NativeModules.js文件中導(dǎo)出的就是上面在原生中創(chuàng)建的全局變量nativeModuleProxy。
NativeModules.Test 最終會調(diào)用JSINativeModules::getModule的方法思犁,這個方法主要做了這些代虾。
- 通過模塊名字對應(yīng)的class,然后通過class生成一個數(shù)組激蹲,數(shù)組一共有5個元素棉磨,第一個元素是模塊名字,第二個元素是需要導(dǎo)出的常量学辱,第三個元素也是一個數(shù)組乘瓤,包含所有導(dǎo)出的方法名字环形,第四個是所有導(dǎo)出的支持Promise的方法的下標(biāo),最后一個是所有導(dǎo)出的同步方法的下標(biāo)衙傀。
- RN是通過遍歷所有的類方法抬吟,如果發(fā)現(xiàn)類方法是
__rct_export__
開頭的,則會調(diào)用這個方法统抬,獲取其返回的RCTMethodInfo火本,如果RCTMethodInfo中的jsName為空,則會取其中的objcName的第一個冒號之前的字符串為jsName聪建,如果objcName中包含RCTPromise則會認(rèn)為這是promise,如果isSync為YES钙畔,則會認(rèn)為這是一個同步方法 - 獲取到配置信息之后會調(diào)用rn端的全局函數(shù)
__fbGenNativeModule
,也定義在NativeModules.js的87行中金麸。global.__fbGenNativeModule = genModule;
- genModule的代碼如下擎析,去掉了異常處理的代碼
下面代碼中的module就是NativeModules.Test這個屬性的值,在遍歷所有方法的過程挥下,用方法名字為屬性名字揍魂,genMethod(moduleID, methodID, methodType);的結(jié)果為值。genMethod的返回值也是一個函數(shù)
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實現(xiàn)是方法類型是則調(diào)用BatchedBridge.callNativeSyncHook的方法见秽,如果是異步的方法則調(diào)用BatchedBridge.enqueueNativeCall,如果是promise的讨盒,則用Promise做一層封裝解取,再調(diào)用了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會調(diào)用上面的全局函數(shù)nativeFlushQueueImmediate返顺,BatchedBridge.callNativeSyncHook會調(diào)用nativeCallSyncHook禀苦,至于這兩個函數(shù)最終是怎么分發(fā)到每個具體的方法里的,下篇文章見吧遂鹊!
寫在最后的話
在寫這篇文章之前振乏,這些代碼已經(jīng)翻過N遍了,但是真正也起來還是比較亂秉扑。寫的真累...自己看明白和能寫出來真不是一回事