浅析ReactNative之通信机制(一)


自从Facebook提出了react之后,这个框架的关注度一直居高不下,它所引入的一些东西还是值得学习,比如组件化的开发方式,virtual dom的性能提升方式等,最近为了改进现有的跨平台方案也在研究react,在这边也做下相关的记录。

pre

在开始使用react之前我们需要搭建相应的环境,这个就不在探讨了,具体可以查看官方文档,由于react需要使用javascript语言,所以可能需要去简单了解一下相关的语法(比如:ES6 标准入门),另外iOS在7.0之后引入了javascriptcore框架,极大的方便了js跟oc之间de通信,之前有一篇博客简单的介绍了javascriptcore,有兴趣的也可以去了解一下。

整体框架

react的整体示意图可以用下面图表示,我们所编写的js代码可以在各个平台上运行,这让我们有web的开发效率的同时又有了原生应用的体验。

Learn once, write anywhere

不过这里面包含的东西对于有点多,尤其很多web相关的东西对于没接触过的人还是有些难度的,要想快速的研究透彻可能不太现实,至少对于我是这样的,因此我们首先研究一下react中js跟native之间的通信方法,其它的有待后面在分析。
假设你已经搭建好相关环境,通过下面的命令创建一个新工程:

react-native init TestDemo

运行xcodeproj工程,Xcode在编译完后会执行打包脚本,将js文件都打包到一个main.jsbundle文件里,我们可以选择将该文件放到服务器上或者应用内部,如果放到服务器上需要先下载该文件,接着加载执行该文件可以看到demo页面了。

packager

demo

通信过程

所谓的通信其实就是js和oc两者如何相互调用传参等,为了更方便的揭示两者的通信过程,我们可以设置messagequeue.js文件中的SPY_MODE标志为true:

//MessageQueue.js,需要处于dev模式
//http://localhost:8081/index.ios.bundle?platform=ios&dev=true为true
let SPY_MODE = true; //

现在重新reload js你就可以看到如下的日志输出,下面的日志可以比较直观的揭示两者的调用方式,'JS->N'即JS调用native代码,反之亦然。可以看到程序一开始native会调用js的RCTDeviceEventEmitter.emit方法,分别发送'appStateDidChange' 和'networkStatusDidChange'两个事件,接着调用js的AppRegistry.runApplication方法启动js应用,然后js层就可以通过native提供的方法来 RCTUIManager.createView来创建视图了。

N->JS : RCTDeviceEventEmitter.emit(["appStateDidChange",{"app_state":"active"}])
N->JS : RCTDeviceEventEmitter.emit(["networkStatusDidChange",{"network_info":"wifi"}])
N->JS : AppRegistry.runApplication(["TestDemo",{"rootTag":1,"initialProps":{}}])
Running application "TestDemo" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF
JS->N : RCTUIManager.createView([2,"RCTView",1,{"flex":1}])
JS->N : RCTUIManager.createView([3,"RCTView",1,{"flex":1}])
JS->N : RCTUIManager.createView([4,"RCTView",1,{"flex":1,"justifyContent":"center","alignItems":"center","backgroundColor":4294311167}])
S->N : RCTUIManager.createView([5,"RCTText",1,{"fontSize":20,"textAlign":"center","margin":10,"accessible":true,"allowFontScaling":true}])
JS->N : RCTUIManager.createView([6,"RCTRawText",1,{"text":"Welcome to React Native!"}])
JS->N : RCTUIManager.setChildren([5,[6]])
JS->Native,JS调用Native

让我们先来看看native如何创建一个模块然后暴露给js层调用的,具体的可以参考官方文档,我们这里举个简单的🌰,创建一个MyModule模块:

@interface MyModule : NSObject <RCTBridgeModule>
@end

@implementation MyModule

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
  RCTLogInfo(@"add an event %@ at %@", name, location);
}

我们先来看看这上面的两个宏定义:

  • RCT_EXPORT_MODULE()
    在native层创建的模块需要通过这个宏定义将该模块暴露给js,该宏定义的具体实现也很简单,如下:
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

首先它将RCTRegisterModule这个函数定义为extern,这样该函数的实现对编译器不可见,但会在链接的时候可以获取到;同时声明一个moduleName函数,该函数返回该模块的js名称,如果你没有指定,默认使用类名;最后声明一个load函数(当应用载入后会加载所有的类,load函数在类初始化加载的时候就调用),然后调用RCTRegisterModule函数注册该模块,该模块会被注册添加到一个全局的数组RCTModuleClasses中。

  • RCT_EXPORT_METHOD()
    要暴露给js调用的API接口需要通过该宏定义声明,该宏定义会额外创建一个函数,形式如下:
+ (NSArray *)__rct_export__230
{
  return @[ @"", @"addEvent:(NSString *)name location:(NSString *)location" ];
}

该函数名称以 rct_export 开头,同时加上该函数所在的代码行数,该函数返回一个包含可选的js名称以及一个函数签名的数组,他们的作用后面会说到。

  • RCTBatchedBridge
    为了桥接js跟native,native层引入了RCTBridge这个类负责双方的通信,不过真正起作用的是RCTBatchedBridge这个类,这个类应该算是比较重要的一个类了,让我们来看看这个类主要做啥事情:
//RCTBatchedBridge.m
- (void)start
{
  dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);

  // 异步的加载打包完成的js文件,也就是main.jsbundle,如果包文件在本地则直接加载,否则根据URL通过NSURLSession方式去下载
  [self loadSource:^(NSError *error, NSData *source) {}];

  // 同步初始化需要暴露给给js层的native模块
  [self initModules];

  //异步初始化JS Executor,也就是js引擎
  dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
    [weakSelf setUpExecutor];
  });

  //异步获取各个模块的配置信息
  dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
    config = [weakSelf moduleConfig];
  });

  //获取各模块的配置信息后,将这些信息注入到JS环境中
  [self injectJSONConfiguration:config onComplete:^(NSError *error) {}];

  //开始执行main.jsbundle
  [self executeSourceCode:sourceCode];
}

简单解释一下其中几个步骤的具体内容:

initModules
- (void)initModules
{
  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
  NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
  SEL setBridgeSelector = NSSelectorFromString(@"setBridge:");
  IMP objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];

  //RCTGetModuleClasses()返回之前提到的全局RCTModuleClasses数组,也就是模块类load时候会注册添加的数组
  for (Class moduleClass in RCTGetModuleClasses()) {
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
    //如果该类或者父类没有重写了init方法或实现了setBridge方法,则,创建一个类的实例
    //React认为开发者期望这个模块在bridge第一次初始化时会实例化,确保该模块只有一个实例对象
    if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod ||
        [moduleClass instancesRespondToSelector:setBridgeSelector]) {
      module = [moduleClass new];
    }

    //创建RCTModuleData模块信息,并保存到数组中
    RCTModuleData *moduleData;
    if (module) {
      if (module != (id)kCFNull) {
        moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
                                                            bridge:self];
      }
    } 
    moduleDataByName[moduleName] = moduleData;
    [moduleDataByID addObject:moduleData];
  }
}

当创建完模块的实例对象之后,会将该实例保存到一个RCTModuleData对象中,RCTModuleData里包含模块的类名,名称,方法列表,实例对象、该模块代码执行的队列以及配置信息等,js层就是根据这个对象来查询该模块的相关信息。

setUpExecutor

reactnative的js引擎在初始化的时候会创建一个新的线程,该线程的优先级跟主线层的优先级一样,同时创建一个runloop,这样线程才能循环执行不会退出。所以执行js代码不会影响到主线程,而且RCTJSCExecutor使用的是JavaScriptCore框架,所以react只支持iOS7及以上的版本。

//RCTJSCExecutor.m
- (instancetype)init
{
  NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[self class]
                                                       selector:@selector(runRunLoopThread)
                                                         object:nil];
  javaScriptThread.name = @"com.facebook.React.JavaScript";
  //设置该线程的优先级处于高优先级
  if ([javaScriptThread respondsToSelector:@selector(setQualityOfService:)]) {
    [javaScriptThread setQualityOfService:NSOperationQualityOfServiceUserInteractive];
  } else {
    javaScriptThread.threadPriority = [NSThread mainThread].threadPriority;
  }
  [javaScriptThread start];
  return [self initWithJavaScriptThread:javaScriptThread context:nil];
}
- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block
{
  __weak RCTJSCExecutor *weakSelf = self;
  [self executeBlockOnJavaScriptQueue:^{
    //将该block函数添加到js的context中,javascriptcore会将block函数转成js function
    weakSelf.context.context[name] = block;
  }];
}

- (void)setUp
{
  [self addSynchronousHookWithName:@"noop" usingBlock:^{}];
  [self addSynchronousHookWithName:@"nativeRequireModuleConfig" usingBlock:^NSString *(NSString *moduleName) {
    //获取该模块的具体配置信息,包含方法以及导出的常量等信息
    NSArray *config = [strongSelf->_bridge configForModuleName:moduleName];
    NSString *result = config ? RCTJSONStringify(config, NULL) : nil;
    return result;
  }];
  [self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){
    [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
  }];
}

可以看到setup的时候会注册几个方法到js的上下文中供后面js调用,比如'nativeFlushQueueImmediate' 和 'nativeRequireModuleConfig'方法等,当js调用相应方法时会执行对应的block,javascriptcore框架会负责js function和block的转换。

moduleConfig
- (NSString *)moduleConfig
{
  NSMutableArray<NSArray *> *config = [NSMutableArray new];
  for (RCTModuleData *moduleData in _moduleDataByID) {
    [config addObject:@[moduleData.name]]; 
  }
  return RCTJSONStringify(@{
    @"remoteModuleConfig": config,
  }, NULL);
}

从实现可以看出仅仅该过程是将模块的名称保存到一个数组中,然后生成一个json字符串的配置信息,包含所有的模块名称,类似如下:

{"remoteModuleConfig":[["MyModule"],["RCTStatusBarManager"],["RCTSourceCode"],["RCTAlertManager"],["RCTExceptionsManager"],...]}
injectJSONConfiguration
- (void)injectJSONConfiguration:(NSString *)configJSON
                     onComplete:(void (^)(NSError *))onComplete
{
  [_javaScriptExecutor injectJSONText:configJSON
                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
                             callback:onComplete];
}

当我们生成配置信息之后,通过上面的函数将该json信息保存到js的全局对象__fbBatchedBridgeConfig中,这样js层就可以知道我们提供了哪些模块,不过细心的话你可能会发现给js的信息只有这些模块的名称,那js怎么调用native的方法的,其实这是react为了懒加载而采用的方式,具体我们下面说明。

当我们配置好native模块后,js层要想调用该native模块的方法如下示例:

var myModule = require('react-native').NativeModules.MyModule;
myModule.addEvent('Birthday Party', '4 Privet Drive, Surrey');

可以看出native模块是保存在NativeModules中,所以让我们到NativeModules.js文件中看看:

const BatchedBridge = require('BatchedBridge');
const RemoteModules = BatchedBridge.RemoteModules;
const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
  Object.defineProperty(NativeModules, moduleName, {
    enumerable: true,
    //懒加载方式
    get: () => {
      let module = RemoteModules[moduleName];
      if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {

        //从native层获取该模块的具体配置信息,nativeRequireModuleConfig是之前注册到js global对象的方法,然后把config信息交给BatchedBridge处理
        const json = global.nativeRequireModuleConfig(moduleName);
        const config = json && JSON.parse(json);
        module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
        RemoteModules[moduleName] = module;
      }
      return module;
    },
  });
});

从上面的代码可以看出,假如你在js层没有使用到native模块,那么这些模块是不会加载到js层的,只有使用到了该模块,react才会去获取该模块的具体配置信息然后加载到js,这是react懒加载的一个方式,让我们能够节约内存,让我们看看如何获取模块的配置信息:

//只会导出有__rct_export__前缀的方法,也就是之前RCT_EXPORT_METHOD这个宏定义提到的
- (NSArray<id<RCTBridgeMethod>> *)methods
{
  //拷贝该类的所有方法,然后过滤以__rct_export__开头的方法
  Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount);
  for (unsigned int i = 0; i < methodCount; i++) {
    Method method = methods[i];
    SEL selector = method_getName(method);
    if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
      IMP imp = method_getImplementation(method);
      NSArray<NSString *> *entries =
        ((NSArray<NSString *> *(*)(id, SEL))imp)(_moduleClass, selector);

      [moduleMethods addObject:/*代表该方法的对象*/];
    }
}

//获取模块的具体配置信息,以数组形式返回,第一个为模块名称,第二个为需要导出的常量(如果有),第三个为导出的方法(如果有)
//以MyModule为例,导出的config为:["MyModule",{"FirstDay":"Monday"},["addEvent","findEvents"]]
- (NSArray *)config
{
  //过滤获取以__rct_export__开头的方法
  for (id<RCTBridgeMethod> method in self.methods) {
    [methods addObject:method.JSMethodName];
  }
  NSMutableArray *config = [NSMutableArray new];
  [config addObject:self.name];
  if (constants.count) {
    [config addObject:constants];
  }
  if (methods) {
    [config addObject:methods];
  }
  return config;
}

导出的配置信息如下所示,可以看到config里包含的模块名称,导出的常量以及导出的函数等,推荐通过调试工具React Developer Tools打断点来查看:

__fbBatchedBridgeConfig

  • BatchedBridge

上面我们说过获取到模块的具体配置信息之后会交给BatchedBridge处理,之前我们说的是native的bridge,不过js为了桥接native层也引入了BatchedBridge:

//BatchedBridge.js
const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);
//将BatchedBridge添加到js的全局global对象中,
Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });
module.exports = BatchedBridge;

我们看到BatchedBridge是MessageQueue的一个实例,而且是全局唯一的一个实例,作为桥接native的一个关键点,我们来具体深入看一下它的内部实现“。

看一下传递给messageQueue的两个参数

  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,

__fbBatchedBridgeConfig我们之前提到过,是一个全局的js变量,__fbBatchedBridgeConfig.remoteModuleConfig就是之前我们在native层导出的模块配置表.

messageQueue

首先看一下messageQueue里的一些实例变量以及API

//存储native提供的各个模块信息,
this.RemoteModules = {};
//存储js提供的各个模块信息
this._callableModules = {};
//用于存放调用信息队列,有三个数组,分别对应调用的模块,调用的函数和参数信息,也就是一个函数调用由这三个数组拼接而成
this._queue = [[], [], [], 0];
//以moduleID为key,value为moduleName,针对js提供的module
this._moduleTable = {};
//以moduleId为key,value为模块导出的方法,针对js提供的module
this._methodTable = {};
//回调函数数组
this._callbacks = [];
//回调函数对应的索引id
this._callbackID = 0;

let modulesConfig = this._genModulesConfig(remoteModules);
this._genModules(modulesConfig);
localModules && this._genLookupTables(
  this._genModulesConfig(localModules),this._moduleTable, this._methodTable
);

//以moduleId为key,value为moduleName,针对native提供的module
this._remoteModuleTable = {};
//以moduleId为key,value为模块导出的方法,针对native提供module
this._remoteMethodTable = {};

可以看到这个队列里保存着js跟native的模块交互的所有信息。先看一下_genModules方法,该方法会根据config解析每个模块的信息并保存到this.RemoteModules中:

_genModules(remoteModules) {
  remoteModules.forEach((config, moduleID) => {
    this._genModule(config, moduleID);
  });
}

_genModules会历遍所有的remoteModules,根据每个模块的配置信息(如何生成配置信息下面会提到)和module索引ID来创建每个模块

_genModule(config, moduleID) {
  let moduleName, constants, methods, asyncMethods;
  //通过解构赋值的方式提取配置信息中的模块名称,常量(如果有),方法名等
  [moduleName, constants, methods, asyncMethods] = config;
  let module = {};
  methods && methods.forEach((methodName, methodID) => {
    //历遍该config中的方法列表,根据配置信息为每个模块生成js function方法并添加到module对象,
    module[methodName] = this._genMethod(moduleID, methodID, methodType);
  });
  //常量信息assign到该module对象,并将module保存到this.RemoteModules中
  Object.assign(module, constants);
  this.RemoteModules[moduleName] = module;
  return module;
}

_genMethod方法如下,假如方法的type为remoteAsync,也就是异步方法,其实就是用一个promise对象(promise是js中的一种异步编程方式)来包装普通的方法,这里我们只看下普通方法的处理过程:

  _genMethod(module, method, type) {
    let fn = null;
    let self = this;
    fn = function(...args) {
    let lastArg = args.length > 0 ? args[args.length - 1] : null;
    let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
    let hasSuccCB = typeof lastArg === 'function';
    let hasErrorCB = typeof secondLastArg === 'function';
    hasErrorCB && invariant(
      hasSuccCB,
      'Cannot have a non-function arg after a function arg.'
    );
    let numCBs = hasSuccCB + hasErrorCB;
    let onSucc = hasSuccCB ? lastArg : null;
    let onFail = hasErrorCB ? secondLastArg : null;
    args = args.slice(0, args.length - numCBs);
    return self.__nativeCall(module, method, args, onFail, onSucc);
      };
    }
    fn.type = type;
    return fn;
  }

可以看到该方法也比较简单,只是在参数列表中提取onFail和onSucc回调函数,并最终调用__nativeCall方法。

  __nativeCall(module, method, params, onFail, onSucc) {
    if (onFail || onSucc) {
      onFail && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onFail;
      onSucc && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onSucc;
    }

    this._queue[MODULE_IDS].push(module);
    this._queue[METHOD_IDS].push(method);
    this._queue[PARAMS].push(params);

    var now = new Date().getTime();
    //当两次调用间隔过小的时候只是先缓存调用信息
    if (global.nativeFlushQueueImmediate &&
        now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
      global.nativeFlushQueueImmediate(this._queue);
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
    }
  }
//RCTJSCExecutor.m
  [self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){
    [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
  }];

__nativeCall方法中,假如有回调参数onFail或onSucc,会将对应的callbackID保存到参数中,并将它们压入到_callbacks栈中;接着将模块,名称以及参数分别保存到_queue的三个数组中;接下来的关键就是调用nativeFlushQueueImmediate方法,该方法是之前RCTJSCExecutor setup的时候注册到js global的方法,因此它会执行相应的native block方法(javascriptcore框架会负责js function和block的转换),可以看出_queue中的模块、方法以及参数信息最终会传递给native层,由native解析并执行相应的native方法。
我们也可以注意到这里react为了性能的优化,当js两次调用方法的间隔小于MIN_TIME_BETWEEN_FLUSHES_MS(5ms)时间,会将调用信息先缓存到_queue中,等待下次在一并提交给native层执行,可能这也就是这些参数设置成数组形式保存的原因。

让我们在接下去看看handleBuffer,handleBuffer会将调用信息先按模块的队列分好,

//RCTBatchedBridge.m
- (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded
{
  NSArray *requestsArray = [RCTConvert NSArray:buffer];
  //先将messageueue传递的参数提取出来分别放到moduleIDs、methodIDs和paramsArrays数组中,
  NSArray<NSNumber *> *moduleIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldRequestModuleIDs]];
  NSArray<NSNumber *> *methodIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldMethodIDs]];
  NSArray<NSArray *> *paramsArrays = [RCTConvert NSArrayArray:requestsArray[RCTBridgeFieldParamss]];

  //将调用的信息先安模块各自指定的队列分好
  NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
                                                  valueOptions:NSPointerFunctionsStrongMemory
                                                      capacity:_moduleDataByName.count];
  [moduleIDs enumerateObjectsUsingBlock:^(NSNumber *moduleID, NSUInteger i, __unused BOOL *stop) {
    RCTModuleData *moduleData = _moduleDataByID[moduleID.integerValue];
    dispatch_queue_t queue = moduleData.methodQueue;
    if (!set) {
      set = [NSMutableOrderedSet new];
      [buckets setObject:set forKey:moduleData.methodQueue];
    }
    [set addObject:@(i)];
  }];
  //按队列来批量执行相应的调用
  for (dispatch_queue_t queue in buckets) {
    dispatch_block_t block = ^{
      RCTProfileEndFlowEvent();
      NSOrderedSet *calls = [buckets objectForKey:queue];
      @autoreleasepool {
        for (NSNumber *indexObj in calls) {
          NSUInteger index = indexObj.unsignedIntegerValue;
          //在各自模块上根据参数执行指定的方法
          [self _handleRequestNumber:index
                            moduleID:[moduleIDs[index] integerValue]
                            methodID:[methodIDs[index] integerValue]
                              params:paramsArrays[index]];
        }
      }
    };
    if (queue == RCTJSThread) {
      [_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
    } else if (queue) {
      dispatch_async(queue, block);
    }
  }
}

_handleRequestNumber根据模块的ID、方法ID以及参数来调用具体的函数:

- (BOOL)_handleRequestNumber:(NSUInteger)i
                    moduleID:(NSUInteger)moduleID
                    methodID:(NSUInteger)methodID
                      params:(NSArray *)params
{
  RCTModuleData *moduleData = _moduleDataByID[moduleID];
  id<RCTBridgeMethod> method = moduleData.methods[methodID];
  [method invokeWithBridge:self module:moduleData.instance arguments:params];
}

其中如何根据函数签名来调用函数可以具体查阅-(void)processMethodSignature函数,这里就不去细谈了。

  • 回调函数
    当有回调函数的时候,之前看到__nativeCall会将callbackID放置在参数中,对应的回调函数插入到_callbacks中保存,js将该ID传递给native,native就是通过该ID来找到对应的回调函数的。
//MyModule.m
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{
  NSArray *events = @[@"test1",@"test2",@"test3"];
  callback(@[[NSNull null], events]);
}
//生成的函数签名
findEvents:(RCTResponseSenderBlock)callback

比如MyModule定义的回调函数,当通过函数签名如果发现参数的类型是RCTResponseSenderBlock,则js传递过来的参数就是回调函数的ID,native层就会根据该ID以及RCTResponseSenderBlock提供的参数来回调相应的js回调函数,整个调用过程可以简单的用下图表示。

调用过程

Native->JS,Native调用JS

假如你有自己创建的js模块想要被native层调用,也需要将该js模块注册添加到messagequeue的_callableModules中,比如reactjs的事件发送模块:

//RCTEventEmitter.js
BatchedBridge.registerCallableModule(
  'RCTEventEmitter',
  ReactNativeEventEmitter
);
注册js模块

native层调用的js方法类似如下:

  [_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
                    args:body ? @[body[@"target"], name, body] : @[body[@"target"], name]];

不过让我们来看看真正执行js代码的地方,里面其实就是用到javascriptcore框架,为了方便断点调试我把宏去掉了,不过不影响,简单示例如下:

- (void)_executeJSCall:(NSString *)method
             arguments:(NSArray *)arguments
              callback:(RCTJavaScriptCallback)onComplete
{
  dispatch_block_t myBlock = ^{
    JSGlobalContextRef contextJSRef = JSContextGetGlobalContext(strongSelf->_context.ctx);
    JSObjectRef globalObjectJSRef = JSContextGetGlobalObject(strongSelf->_context.ctx);
    
    //从js的全局对象中获取BatchedBridge对象
    JSStringRef moduleNameJSStringRef = JSStringCreateWithUTF8CString("__fbBatchedBridge");
    JSValueRef moduleJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef);
    JSStringRelease(moduleNameJSStringRef)
    if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) {
      //获取js对象相应的方法
      JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method);
      JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef);
      JSStringRelease(methodNameJSStringRef);
      if (methodJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, methodJSRef)) {
        // 调用相应的js函数
        if (arguments.count == 0) {
          resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef);
        }
  };
  [self executeBlockOnJavaScriptQueue:myBlock];
}

可以看出native调用js的代码借助JavaScriptCore框架变的非常简单。

总结

上面简单的说明了react之间的通信过程,至于跟view相关的内容下次在讨论,这个其实才是react比较重要的内容,如果有兴趣欢迎一起交流~

参考

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

推荐阅读更多精彩内容