文章首发集体博客: ReactNative与iOS通信原理解析-通信篇

导语:其实本来是想编写一篇 react-native (下文简称 rn) 在 iOS 中如何实现 jsbridge 的文章;置信看过官网文档的同学都分明 rn 和 iOS 通信应用了一个叫RCTBridgeModule的模块去实现;置信大家与我一样,不能知其然不知其所以然;所以决定去翻一番 rn 的源码,一探其 rn 与 iOS 通信的机制。后果随着剖析的深刻发现内容较多;于是编写了 ReactNative 与 iOS 原生通信原理解析-初始化 和 ReactNative 与 iOS 原生通信原理解析-JS 加载及执行篇 两篇 RN 源码剖析文章。

本文将在上述两篇文章的根底上,持续深刻了解 RN 与 iOS 原生通信机制。

申明: 本文所应用的 rn 版本为0.63.0

缘起

看过后面一篇ReactNative 与 iOS 原生通信原理解析-JS 加载及执行篇的同学应该曾经分明,在执行实现 js 代码之后,会在 JSIExecutor 中执行 flush 函数;flush 函数中会在首次时对 JS 和 native 进行绑定;在绑定之后 native 就能够调用 JS 函数,实现 native to js 之间的通信。

// 各种js办法向native的绑定void JSIExecutor::bindBridge() {  std::call_once(bindFlag_, [this] {    // 通过js侧的__fbBatchedBridge获取对应的batchedBridge    Value batchedBridgeValue =        runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");    if (batchedBridgeValue.isUndefined()) {      throw JSINativeException(          "Could not get BatchedBridge, make sure your bundle is packaged correctly");    }// 把batchedBridge中的callFunctionReturnFlushedQueue 和 JSIExecutor对象的callFunctionReturnFlushedQueue_进行绑定    Object batchedBridge = batchedBridgeValue.asObject(*runtime_);    callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(        *runtime_, "callFunctionReturnFlushedQueue");  // 把batchedBridge中的invokeCallbackAndReturnFlushedQueue 和 JSIExecutor中的invokeCallbackAndReturnFlushedQueue_进行绑定;    invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(        *runtime_, "invokeCallbackAndReturnFlushedQueue");  // 把batchedBridge中的flushedQueue 和 JSIExecutor中的flushedQueue_进行绑定。    flushedQueue_ =        batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");  });}

好啦,咱们当初曾经晓得在整个 JS 执行实现之后会进行 js 函数和 native 的绑定;那么 native 是如何执行 JS 函数的呢?上面咱们一起来理解。

Native to JS

不知您是否还记得,native 在执行 js 代码的时候,有一个回调函数,函数外部通过事件的形式告诉 RCTRootView javascript 曾经加载。

// js代码的执行  - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync{  // js代码执行回调  dispatch_block_t completion = ^{    // 当js代码执行实现,须要刷新js执行事件队列    [self _flushPendingCalls];    // 在主线程中告诉RCTRootView; js代码曾经执行结束;当RCTRootView接管到告诉就会挂在并展现    dispatch_async(dispatch_get_main_queue(), ^{      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification                                                          object:self->_parentBridge                                                        userInfo:@{@"bridge" : self}];      [self ensureOnJavaScriptThread:^{        // 定时器继续执行        [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];      }];    });  };  if (sync) {    // 同步执行js代码    [self executeApplicationScriptSync:sourceCode url:self.bundleURL];    completion();  } else {    // 异步执行js代码    [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];  }  [self.devSettings setupHotModuleReloadClientIfApplicableForURL:self.bundleURL];}

RCTRootView 中会监听 RCTJavaScriptDidLoadNotification 事件;并执行如下办法:

 (void)javaScriptDidLoad:(NSNotification *)notification{  // 获取到RCTBridge的实例batchedBridge(可能有点超前了,前面会将)  RCTBridge *bridge = notification.userInfo[@"bridge"];  if (bridge != _contentView.bridge) {    [self bundleFinishedLoading:bridge];  }}- (void)bundleFinishedLoading:(RCTBridge *)bridge{  // ...  [_contentView removeFromSuperview];  _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds                                                    bridge:bridge                                                  reactTag:self.reactTag                                           sizeFlexiblity:_sizeFlexibility];  // 利用RCTBridge调用js办法,启动页面  [self runApplication:bridge];  // 展现页面  [self insertSubview:_contentView atIndex:0];}- (void)runApplication:(RCTBridge *)bridge{  NSString *moduleName = _moduleName ?: @"";  NSDictionary *appParameters = @{    @"rootTag" : _contentView.reactTag,    @"initialProps" : _appProperties ?: @{},  };   // 调用RCTCxxBridge的enqueueJSCall:method:args:completion:办法  [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL];}

对于 bridge enqueueJSCall, rn 会着 Instance->NativeToJsBridge->JSIExecutor 这个调用链调用了 JSIExecutor::callFunction 办法,办法内调用了 JSIExecutorcallFunctionReturnFlushedQueue_办法。

在 bindBridge 中callFunctionReturnFlushedQueue_是通过 runtime 的形式将 native 的callFunctionReturnFlushedQueue_指向了 js 中的callFunctionReturnFlushedQueue函数的。

native 将 moduleId、methodId、arguements 作为参数执行 JS 侧的 callFunctionReturnFlushedQueue 函数,函数会返回一个 queue;这个 queue 就是 JS 须要 native 侧执行的办法;最初 native 侧交给callNativeModules去执行对应的办法。

js 侧应用 callFunction 获取到指定的 modulemethod;应用 apply 执行对应办法。

// RCTxxBridge.mm- (void)enqueueJSCall:(NSString *)module               method:(NSString *)method                 args:(NSArray *)args           completion:(dispatch_block_t)completion{    if (strongSelf->_reactInstance) {        // 调用了Instance.callJSFunction      strongSelf->_reactInstance->callJSFunction(          [module UTF8String], [method UTF8String], convertIdToFollyDynamic(args ?: @[]));    }  }];}// Instance.cppvoid Instance::callJSFunction(    std::string &&module,    std::string &&method,    folly::dynamic &&params) {  callback_->incrementPendingJSCalls();  // 调用NativeToJsBridge的callFunction  nativeToJsBridge_->callFunction(      std::move(module), std::move(method), std::move(params));}// NativeToJsBridge.cppvoid NativeToJsBridge::callFunction(    std::string &&module,    std::string &&method,    folly::dynamic &&arguments) {  runOnExecutorQueue([this,                      module = std::move(module),                      method = std::move(method),                      arguments = std::move(arguments),                      systraceCookie](JSExecutor *executor) {    // 调用了JSIExecutor中的callFunction    executor->callFunction(module, method, arguments);  });}// JSIExecutor.cppvoid JSIExecutor::callFunction(    const std::string &moduleId,    const std::string &methodId,    const folly::dynamic &arguments) {// 如果还未将callFunctionReturnFlushedQueue_和js函数中的callFunctionReturnFlushedQueue函数进行绑定,那么首先进行绑定  if (!callFunctionReturnFlushedQueue_) {    bindBridge();  }  Value ret = Value::undefined();  try {    scopedTimeoutInvoker_(        [&] {        // 调用callFunctionReturnFlushedQueue_  传入JS moduleId、methodId、arguements 参数,JS侧会返回queue          ret = callFunctionReturnFlushedQueue_->call(              *runtime_,              moduleId,              methodId,              valueFromDynamic(*runtime_, arguments));        },        std::move(errorProducer));  } catch (...) {  }// 执行native modules  callNativeModules(ret, true);}// MessageQueue.js callFunctionReturnFlushedQueue(    module: string,    method: string,    args: any[],  ): null | [Array<number>, Array<number>, Array<any>, number] {    this.__guard(() => {      this.__callFunction(module, method, args);    });    return this.flushedQueue();  }    __callFunction(module: string, method: string, args: any[]): void {    this._lastFlush = Date.now();    this._eventLoopStartTime = this._lastFlush;    const moduleMethods = this.getCallableModule(module);    moduleMethods[method].apply(moduleMethods, args);  }

除开方才咱们讲过的callFunctionReturnFlushedQueue_和 js 侧的callFunctionReturnFlushedQueue函数进行绑定过,还有invokeCallbackAndReturnFlushedQueueflushedQueue也有绑定。此处就不做过多解说,有趣味的同学能够去查阅一下
invokeCallbackAndReturnFlushedQueueflushedQueue;其实现原理和 callFunctionReturnFlushedQueue是相似的。

流程图请见文末!

JS to Native

what, 都在后面 native to js 中讲过 启动AppRegistry.runApplication了;页面都启动了;为啥还不讲 js to native呢? 讲真,不是笔者偷懒,而是想在您在晓得 RN 初始化整体流程RN 的 jsbundle 加载及执行流程以及native 调用 JS三座大山的根底只是之后,再深刻去理解 JS 调用 native。

js to native 可能比拟绕,咱们先来看一下整个流程:

RN 官网文档通知咱们,能够应用 NativeModules 和 iOS 进行通信;那么咱们先来看看在 JS 端,咱们是如何应用 NativeModules 的。

import { NativeModules } from "react-native";// 获取到本人在iOS端的native module :ReactJSBridgeconst JSBridge = NativeModules.ReactJSBridge;// 调用对应Module的对应办法JSBridge.callWithCallback();

重点就在 NativeModules 这个模块,在 react-native 源码中,NativeModules == global.nativeModuleProxy == native 的 NativeModuleProxy的;在之前的 rn 初始化阶段讲过在 NativeToJsBridge 初始化的时候会调用 JSIExecutorinitializeRuntime;初始化一些 js 和 native 之间的桥梁。

let NativeModules: { [moduleName: string]: Object, ... } = {};if (global.nativeModuleProxy) {  NativeModules = global.nativeModuleProxy;}
// NativeToJsBridge.cppvoid NativeToJsBridge::initializeRuntime() {  runOnExecutorQueue(      [](JSExecutor *executor) mutable { executor->initializeRuntime(); });}// JSIExecutor.cppvoid JSIExecutor::initializeRuntime() {  SystraceSection s("JSIExecutor::initializeRuntime");  runtime_->global().setProperty(      *runtime_,      "nativeModuleProxy",      Object::createFromHostObject(          *runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));}

在 JS 侧调用NativeModules.本人的模块名称也同步会触发 native 端的NativeModuleProxy::get办法;并同步调用JSINativeModules::getModuleJSINativeModules::createModule办法;在JSINativeModules::createModule办法中会利用 js 端的__fbGenNativeModule获取 Module 信息。查阅 JS 端的__fbGenNativeModule函数,发现__fbGenNativeModule==JS 侧的 genModule 办法

// JSIExecutor.cpp NativeModuleProxy  Value get(Runtime &rt, const PropNameID &name) override {    if (name.utf8(rt) == "name") {      return jsi::String::createFromAscii(rt, "NativeModules");    }    auto nativeModules = weakNativeModules_.lock();    if (!nativeModules) {      return nullptr;    }    return nativeModules->getModule(rt, name);  }// JSINativeModules.cppValue JSINativeModules::getModule(Runtime &rt, const PropNameID &name) {  if (!m_moduleRegistry) {    return nullptr;  }  std::string moduleName = name.utf8(rt);  const auto it = m_objects.find(moduleName);  if (it != m_objects.end()) {    return Value(rt, it->second);  }  auto module = createModule(rt, moduleName);  if (!module.hasValue()) {    return nullptr;  }  auto result =      m_objects.emplace(std::move(moduleName), std::move(*module)).first;  return Value(rt, result->second);}folly::Optional<Object> JSINativeModules::createModule(    Runtime &rt,    const std::string &name) {  if (!m_genNativeModuleJS) {    m_genNativeModuleJS =        rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");  }  auto result = m_moduleRegistry->getConfig(name);  Value moduleInfo = m_genNativeModuleJS->call(      rt,      valueFromDynamic(rt, result->config),      static_cast<double>(result->index));  folly::Optional<Object> module(      moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));  return module;}

JS 侧的 getModule 函数,会利用 native module 传递的 Module 信息(moduleName,moduleInfo),将以后须要执行的函数塞入队列中BatchedBridge.enqueueNativeCall。等 native 过去调 JS 的任意办法时,再把这个队列返回给 native,此时 native 再执行这个队列里要调用的办法。

如果 native 迟迟不调用 JS,JS 规定了一个工夫阈值,这阈值是 5ms,如果超过 5ms后仍旧没有 native call JS。那么 JS 就会被动触发队列的刷新,即立刻让 native 侧执行队列中缓存的一系列的办法。

// NativeModules.jsfunction genModule(  config: ?ModuleConfig,  moduleID: number): ?{  name: string,  module?: Object,  ...} {  const [moduleName, constants, methods, promiseMethods, syncMethods] = config;  if (!constants && !methods) {    // Module contents will be filled in lazily later    return { name: moduleName };  }  const module = {};  methods &&    methods.forEach((methodName, methodID) => {      const isPromise =        promiseMethods && arrayContains(promiseMethods, methodID);      const isSync = syncMethods && arrayContains(syncMethods, methodID);      const methodType = isPromise ? "promise" : isSync ? "sync" : "async";      // 留神这里,重点,genMethod会将以后Method塞入队列      module[methodName] = genMethod(moduleID, methodID, methodType);    });  Object.assign(module, constants);  return { name: moduleName, module };}// export this method as a global so we can call it from nativeglobal.__fbGenNativeModule = genModule;function genMethod(moduleID: number, methodID: number, type: MethodType) {  let fn = null;  // 如果是promise类型的,须要塞入执行队列  if (type === "promise") {    fn = function promiseMethodWrapper(...args: Array<any>) {      // In case we reject, capture a useful stack trace here.      const enqueueingFrameError: ExtendedError = new Error();      return new Promise((resolve, reject) => {        BatchedBridge.enqueueNativeCall(          moduleID,          methodID,          args,          (data) => resolve(data),          (errorData) =>            reject(updateErrorWithErrorData(errorData, enqueueingFrameError))        );      });    };  } else {    fn = function nonPromiseMethodWrapper(...args: Array<any>) {      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";      const onSuccess = hasSuccessCallback ? lastArg : null;      const onFail = hasErrorCallback ? secondLastArg : null;      const callbackCount = hasSuccessCallback + hasErrorCallback;      args = args.slice(0, args.length - callbackCount);      if (type === "sync") {        return BatchedBridge.callNativeSyncHook(          moduleID,          methodID,          args,          onFail,          onSuccess        );      } else {          // 也要记得塞入队列怕        BatchedBridge.enqueueNativeCall(          moduleID,          methodID,          args,          onFail,          onSuccess        );      }    };  }  fn.type = type;  return fn;}// MessageQueue.js// 工夫阈值const MIN_TIME_BETWEEN_FLUSHES_MS = 5;enqueueNativeCall(    moduleID: number,    methodID: number,    params: any[],    onFail: ?Function,    onSucc: ?Function,  ) {    this.processCallbacks(moduleID, methodID, params, onFail, onSucc);    // 将module,methodName以及参数塞入队列中    this._queue[MODULE_IDS].push(moduleID);    this._queue[METHOD_IDS].push(methodID);    this._queue[PARAMS].push(params);    const now = Date.now();    // 如果native迟迟不调用JS,JS规定了一个工夫阈值,这阈值是5ms,如果超过5ms后仍旧没有native call JS。那么JS就会被动触发队列的刷新,即立刻让native侧执行队列中缓存的一系列的办法。    if (      global.nativeFlushQueueImmediate &&      now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS    ) {      const queue = this._queue;      this._queue = [[], [], [], this._callID];      this._lastFlush = now;      global.nativeFlushQueueImmediate(queue);    }    this.__spy({        type: TO_NATIVE,        module: moduleID + '',        method: methodID,        args: params,      });  }

在 JS 侧应用nativeFlushQueueImmediate立刻调用会触发 native 的 callNativeModules 办法,并执行 native 办法。

// JSIExecutor.cppvoid JSIExecutor::initializeRuntime() {  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();          }));}

至此,js to native的解说曾经结束;当初咱们对 js 调用 native 做一个简略的小结。

  1. js to native,会利用 NativeModules(NativeModules == global.nativeModuleProxy == native 的 NativeModuleProxy)调用 native 侧的getModule->createModule直到调用到js侧的__fbGenNativeModule也就是 js 侧的getModule函数;
  2. js 侧的getModule函数会返回以后 Module 的信息给 native,并将以后的 moduleId,methodId 曾经 params 塞入队列;通过比照高低两次申请的工夫距离是否>5ms,则会利用nativeFlushQueueImmediate立刻调用native modules.

一个疑难?为什么 js 不间接调用 native 而是通过塞入队列的形式

集体了解:js 触发 native 其实是一个很频繁的过程,能够设想 scrollView 的滚动,动画的实现等等,将会带来十分大的性能开销;如果不做缓存立刻执行的话,RN 的整体性能会降落;所以 RN 端利用队列的形式进行 native modules 调用的缓存;以此达到性能优化的目标。

总结

下面咱们曾经学习了 Native to JSJS to Native流程,上面咱们从整体来看一下 js 和 native 是如何交互的。


Native to JS

  1. native 执行实现 js 代码会发送一个RCTJavaScriptDidLoadNotification工夫给 RCTRootView;
  2. RCTRootView 接管工夫后会应用batchedBridge->enqueueJSCall去执行AppRegistry.runApplication函数;启动 RN 页面。
  3. 执行enqueueJSCall的过程会沿着Instance->NativeToJsBridge->JSIExecutor 这个调用链调用了 JSIExecutor::callFunction 办法,办法内调用了 JSIExecutorcallFunctionReturnFlushedQueue_办法。
  4. callFunctionReturnFlushedQueue_因为曾经和 JS 侧的 callFunctionReturnFlushedQueue 办法曾经绑定,所以在执行此 js 函数时会执行 callFunction 办法,应用 jsapply 函数执行module.methodName 的调用。

JS to Native

  1. js to native,会利用 NativeModules(NativeModules == global.nativeModuleProxy == native 的 NativeModuleProxy)调用 native 侧的getModule->createModule直到调用到js侧的__fbGenNativeModule也就是 js 侧的getModule函数;
  2. js 侧的getModule函数会返回以后 Module 的信息给 native,并将以后的 moduleId,methodId 曾经 params 塞入队列;通过比照高低两次申请的工夫距离是否>5ms,则会利用nativeFlushQueueImmediate立刻调用native modules.

ReactNative 与 iOS 原生通信原理解析系列

  • ReactNative 与 iOS 原生通信原理解析-初始化
  • ReactNative 与 iOS 原生通信原理解析-JS 加载及执行篇
  • ReactNative 与 iOS 原生通信原理解析-通信篇