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

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

IOS小彩虹2021-07-12 1:40:2970A+A-

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

  1. iOS端如何操作
    1. 创建一个类,然后遵循协议
    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遍了,但是真正也起来还是比较乱。写的真累...自己看明白和能写出来真不是一回事

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们