ReactNative與iOS原生通訊原理解析-通訊篇

導語:其實本來是想編寫一篇 react-native (下文簡稱 rn) 在 iOS 中如何實現 jsbridge 的文章;相信看過官方文檔的同窗都清楚 rn 和 iOS 通訊使用了一個叫 RCTBridgeModule的模塊去實現;相信你們與我同樣,不能知其然不知其因此然;因此決定去翻一番 rn 的源碼,一探其 rn 與 iOS 通訊的機制。結果隨着分析的深刻發現內容較多;因而編寫了 ReactNative 與 iOS 原生通訊原理解析-初始化ReactNative 與 iOS 原生通訊原理解析-JS 加載及執行篇 兩篇 RN 源碼分析文章。

本文將在上述兩篇文章的基礎上,繼續深刻理解 RN 與 iOS 原生通訊機制。javascript

聲明: 本文所使用的 rn 版本爲0.63.0html

# 緣起

看過前面一篇ReactNative 與 iOS 原生通訊原理解析-JS 加載及執行篇 的同窗應該已經清楚,在執行完成 js 代碼以後,會在 JSIExecutor 中執行 flush 函數;flush 函數中會在首次時對 JS 和 native 進行綁定;在綁定以後 native 就能夠調用 JS 函數,實現 native to js 之間的通訊。java

// 各類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 函數的呢?下面咱們一塊兒來了解。react

# Native to JS

不知您是否還記得,native 在執行 js 代碼的時候,有一個回調函數,函數內部經過事件的方式通知 RCTRootView javascript 已經加載。git

// 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 事件;並執行以下方法:github

(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_方法。react-native

在 bindBridge 中callFunctionReturnFlushedQueue_是經過 runtime 的方式將 native 的callFunctionReturnFlushedQueue_指向了 js 中的callFunctionReturnFlushedQueue函數的。promise

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 原生通訊原理解析系列

相關文章
相關標籤/搜索