为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
iOS

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调用,这里面到底经历了什么?

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

从源码中找寻答案

**

  1. 先来看看 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一共做了两件事,

  1. 实现了类方法moduleName,返回一个字符串(注:在宏定义中#号代表把后面变量前后添加双引号)
  2. 在load方法中调用了RCTRegisterModule,这个方法就是把类对象添加到一个全局的数组中
    复制代码
  3. 再来看下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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
数据库工程师
手记
粉丝
42
获赞与收藏
202

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消