1

文章首发个人博客: 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.cpp
void 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.cpp
void 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.cpp

void 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 :ReactJSBridge
const 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.cpp
void NativeToJsBridge::initializeRuntime() {
  runOnExecutorQueue(
      [](JSExecutor *executor) mutable { executor->initializeRuntime(); });
}
// JSIExecutor.cpp
void 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.cpp
Value 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.js
function 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 native
global.__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.cpp
void 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 原生通信原理解析系列


mrgaogang
8 声望7 粉丝