# ReactNative与iOS原生通信原理解析-通信篇
导语:其实原本是想编写一篇
react-native(下文简称 rn) 在iOS中如何实现jsbridge的文章;相信看过官方文档的同学都清楚 rn 和 iOS 通信使用了一个叫RCTBridgeModule的模块去实现;相信大家与我一样,不能知其然不知其所以然;所以决定去翻一番 rn 的源码,一探其 rn 与 iOS 通信的机制。结果随着分析的深入发现内容较多;于是编写了 ReactNative 与 iOS 原生通信原理解析-初始化 (opens new window) 和 ReactNative 与 iOS 原生通信原理解析-JS 加载及执行篇 (opens new window) 两篇 RN 源码分析文章。
本文将在上述两篇文章的基础上,继续深入理解 RN 与 iOS 原生通信机制。
声明: 本文所使用的 rn 版本为0.63.0。
# 缘起
看过前面一篇ReactNative 与 iOS 原生通信原理解析-JS 加载及执行篇 (opens new window)的同学应该已经清楚,在执行完成 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 方法,方法内调用了 JSIExecutor 的 callFunctionReturnFlushedQueue_方法。
在 bindBridge 中callFunctionReturnFlushedQueue_是通过 runtime 的方式将 native 的callFunctionReturnFlushedQueue_指向了 js 中的callFunctionReturnFlushedQueue函数的。
native 将 moduleId、methodId、arguements 作为参数执行 JS 侧的 callFunctionReturnFlushedQueue 函数,函数会返回一个 queue;这个 queue 就是 JS 需要 native 侧执行的方法;最后 native 侧交给callNativeModules去执行对应的方法。
js 侧使用 callFunction 获取到指定的 module 和 method;使用 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 &¶ms) {
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函数进行绑定过,还有invokeCallbackAndReturnFlushedQueue和flushedQueue也有绑定。此处就不做过多讲解,有兴趣的同学可以去查阅一下
invokeCallbackAndReturnFlushedQueue和flushedQueue;其实现原理和 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 初始化的时候会调用 JSIExecutor 的initializeRuntime;初始化一些 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::getModule和JSINativeModules::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 做一个简单的小结。

js to native,会利用
NativeModules(NativeModules == global.nativeModuleProxy == native 的 NativeModuleProxy)调用 native 侧的getModule->createModule直到调用到js侧的__fbGenNativeModule也就是 js 侧的getModule函数;js 侧的
getModule函数会返回当前 Module 的信息给 native,并将当前的 moduleId,methodId 已经 params 塞入队列;通过对比上下两次请求的时间间隔是否>5ms,则会利用nativeFlushQueueImmediate立即调用native modules.
一个疑问?为什么 js 不直接调用 native 而是通过塞入队列的方式
个人理解:js 触发 native 其实是一个很频繁的过程,可以想象 scrollView 的滚动,动画的实现等等,将会带来非常大的性能开销;如果不做缓存立即执行的话,RN 的整体性能会下降;所以 RN 端利用队列的方式进行 native modules 调用的缓存;以此达到性能优化的目的。
# 总结
上面我们已经学习了 Native to JS和JS to Native流程,下面我们从整体来看一下 js 和 native 是如何交互的。

Native to JS

native 执行完成 js 代码会发送一个
RCTJavaScriptDidLoadNotification时间给 RCTRootView;RCTRootView 接收时间后会使用
batchedBridge->enqueueJSCall去执行AppRegistry.runApplication函数;启动 RN 页面。执行
enqueueJSCall的过程会沿着Instance->NativeToJsBridge->JSIExecutor这个调用链调用了 JSIExecutor::callFunction 方法,方法内调用了JSIExecutor的callFunctionReturnFlushedQueue_方法。callFunctionReturnFlushedQueue_由于已经和 JS 侧的callFunctionReturnFlushedQueue方法已经绑定,所以在执行此 js 函数时会执行callFunction方法,使用js的apply函数执行module.methodName的调用。
JS to Native

js to native,会利用
NativeModules(NativeModules == global.nativeModuleProxy == native 的 NativeModuleProxy)调用 native 侧的getModule->createModule直到调用到js侧的__fbGenNativeModule也就是 js 侧的getModule函数;js 侧的
getModule函数会返回当前 Module 的信息给 native,并将当前的 moduleId,methodId 已经 params 塞入队列;通过对比上下两次请求的时间间隔是否>5ms,则会利用nativeFlushQueueImmediate立即调用native modules.
ReactNative 与 iOS 原生通信原理解析系列
- 本文链接: https://mrgaogang.github.io/react/ReactNative%E4%B8%8EiOS%E5%8E%9F%E7%94%9F%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90-%E9%80%9A%E4%BF%A1%E7%AF%87.html
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!