reactNative调用原生(iOS)方法的全过程(一)

reactNative如何调用原生(iOS)方法

  1. iOS端如何操作
    1. 创建一个类,然后遵循<RCTBridgeModule>协议
    2. 使用RCT_EXPORT_MODULE导出模块
    3. 使用RCT_EXPORT_METHOD导出异步方法
    4. 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
    
    
  2. RN端如何使用?
    1. 导入NativeModules模块
    2. 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);
    
  3. why?
    1. 为什么RCT_EXPORT_METHOD参数中有了RCTPromiseResolveBlock和RCTPromiseRejectBlock在JS调用的时候就支持Promise了?
    2. Test类是什么时候实例化的?
    3. RN端的NativeModules是什么?NativeModules.Test又是什么?
    4. 总之一个疑问,为什么我在原生导出一下,在RN里就能用js调用,这里面到底经历了什么?

如果你能对上面的问题都清楚,那么下面的内容对你应该没什么帮助。

从源码中找寻答案

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);                \
  }
```
根据上面的代码可以看出,RCT_EXPORT_MODULE一共做了两件事,
1. 实现了类方法moduleName,返回一个字符串(注:在宏定义中#号代表把后面变量前后添加双引号)
2. 在load方法中调用了RCTRegisterModule,这个方法就是把类对象添加到一个全局的数组中

2. 再来看下RCT_EXPORT_METHOD和RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD做了什么?

由于这两个宏定义嵌套比较多,下面代码都直接显示宏定义完全展开之后的代码

```c
/// 导出一个普通的异步方法,
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件事

  1. 把当前的类对象添加到一个全局数组内
  2. 生成了一个moduleName类方法,返回供JS调用的时候的模块名字
  3. 生成了一堆以__rct_export__开头的类方法,并返回一个RCTMethodInfo结构体
  4. 生成了一堆真正供js调用的方法

3. 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); }));

4. NativeModules是什么?NativeModules.Test又是什么?

先来看NativeModules,NativeModules是从react-native/Libraries/BatchedBridge/NativeModules.js文件中导出的就是上面在原生中创建的全局变量nativeModuleProxy。

NativeModules.Test 最终会调用JSINativeModules::getModule的方法,这个方法主要做了这些。

  1. 通过模块名字对应的class,然后通过class生成一个数组,数组一共有5个元素,第一个元素是模块名字,第二个元素是需要导出的常量,第三个元素也是一个数组,包含所有导出的方法名字,第四个是所有导出的支持Promise的方法的下标,最后一个是所有导出的同步方法的下标。
  2. RN是通过遍历所有的类方法,如果发现类方法是__rct_export__开头的,则会调用这个方法,获取其返回的RCTMethodInfo,如果RCTMethodInfo中的jsName为空,则会取其中的objcName的第一个冒号之前的字符串为jsName,如果objcName中包含RCTPromise则会认为这是promise,如果isSync为YES,则会认为这是一个同步方法
  3. 获取到配置信息之后会调用rn端的全局函数__fbGenNativeModule,也定义在NativeModules.js的87行中。global.__fbGenNativeModule = genModule;
  4. 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};
}
  1. 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;
}
  1. BatchedBridge是MessageQueue的实例就是常说的消息队列,
  2. BatchedBridge.enqueueNativeCall会调用上面的全局函数nativeFlushQueueImmediate,BatchedBridge.callNativeSyncHook会调用nativeCallSyncHook,至于这两个函数最终是怎么分发到每个具体的方法里的,下篇文章见吧!

写在最后的话

在写这篇文章之前,这些代码已经翻过N遍了,但是真正也起来还是比较乱。写的真累...自己看明白和能写出来真不是一回事

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容