关于react-native:ReactNative与iOS通信原理解析通信篇

2次阅读

共计 13668 个字符,预计需要花费 35 分钟才能阅读完成。

文章首发集体博客: 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 原生通信原理解析系列

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

正文完
 0