reactNative調(diào)用原生(iOS)方法的全過程(一)

reactNative如何調(diào)用原生(iOS)方法

  1. iOS端如何操作
    1. 創(chuàng)建一個類,然后遵循<RCTBridgeModule>協(xié)議
    2. 使用RCT_EXPORT_MODULE導(dǎo)出模塊
    3. 使用RCT_EXPORT_METHOD導(dǎo)出異步方法
    4. 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
    
    
  2. RN端如何使用?
    1. 導(dǎo)入NativeModules模塊
    2. 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);
    
  3. why雷蹂?
    1. 為什么RCT_EXPORT_METHOD參數(shù)中有了RCTPromiseResolveBlock和RCTPromiseRejectBlock在JS調(diào)用的時候就支持Promise了伟端?
    2. Test類是什么時候?qū)嵗模?/li>
    3. RN端的NativeModules是什么?NativeModules.Test又是什么?
    4. 總之一個疑問匪煌,為什么我在原生導(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件事

  1. 把當(dāng)前的類對象添加到一個全局?jǐn)?shù)組內(nèi)
  2. 生成了一個moduleName類方法芽隆,返回供JS調(diào)用的時候的模塊名字
  3. 生成了一堆以__rct_export__開頭的類方法,并返回一個RCTMethodInfo結(jié)構(gòu)體
  4. 生成了一堆真正供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的方法思犁,這個方法主要做了這些代虾。

  1. 通過模塊名字對應(yīng)的class,然后通過class生成一個數(shù)組激蹲,數(shù)組一共有5個元素棉磨,第一個元素是模塊名字,第二個元素是需要導(dǎo)出的常量学辱,第三個元素也是一個數(shù)組乘瓤,包含所有導(dǎo)出的方法名字环形,第四個是所有導(dǎo)出的支持Promise的方法的下標(biāo),最后一個是所有導(dǎo)出的同步方法的下標(biāo)衙傀。
  2. RN是通過遍歷所有的類方法抬吟,如果發(fā)現(xiàn)類方法是__rct_export__開頭的,則會調(diào)用這個方法统抬,獲取其返回的RCTMethodInfo火本,如果RCTMethodInfo中的jsName為空,則會取其中的objcName的第一個冒號之前的字符串為jsName聪建,如果objcName中包含RCTPromise則會認(rèn)為這是promise,如果isSync為YES钙畔,則會認(rèn)為這是一個同步方法
  3. 獲取到配置信息之后會調(diào)用rn端的全局函數(shù)__fbGenNativeModule,也定義在NativeModules.js的87行中金麸。global.__fbGenNativeModule = genModule;
  4. 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};
}
  1. 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;
}
  1. BatchedBridge是MessageQueue的實例就是常說的消息隊列,
  2. BatchedBridge.enqueueNativeCall會調(diào)用上面的全局函數(shù)nativeFlushQueueImmediate返顺,BatchedBridge.callNativeSyncHook會調(diào)用nativeCallSyncHook禀苦,至于這兩個函數(shù)最終是怎么分發(fā)到每個具體的方法里的,下篇文章見吧遂鹊!

寫在最后的話

在寫這篇文章之前振乏,這些代碼已經(jīng)翻過N遍了,但是真正也起來還是比較亂秉扑。寫的真累...自己看明白和能寫出來真不是一回事

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末慧邮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子舟陆,更是在濱河造成了極大的恐慌误澳,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秦躯,死亡現(xiàn)場離奇詭異忆谓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)踱承,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門倡缠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哨免,“玉大人,你說我怎么就攤上這事昙沦∽镣伲” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵桅滋,是天一觀的道長慧耍。 經(jīng)常有香客問我,道長丐谋,這世上最難降的妖魔是什么芍碧? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮号俐,結(jié)果婚禮上泌豆,老公的妹妹穿的比我還像新娘。我一直安慰自己吏饿,他們只是感情好踪危,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著猪落,像睡著了一般贞远。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笨忌,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天蓝仲,我揣著相機(jī)與錄音,去河邊找鬼官疲。 笑死袱结,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的途凫。 我是一名探鬼主播垢夹,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼维费!你這毒婦竟也來了果元?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤犀盟,失蹤者是張志新(化名)和其女友劉穎噪漾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體且蓬,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡欣硼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诈胜。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡豹障,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出焦匈,到底是詐尸還是另有隱情血公,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布缓熟,位于F島的核電站累魔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏够滑。R本人自食惡果不足惜垦写,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望彰触。 院中可真熱鬧梯投,春花似錦、人聲如沸况毅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尔许。三九已至么鹤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間味廊,已是汗流浹背蒸甜。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留毡们,地道東北人迅皇。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓昧辽,卻偏偏與公主長得像衙熔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子搅荞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容