文章首发个人博客: 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 在执行 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, Array, Array, 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
是类似的。
流程图请见文末!
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(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
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) {
// 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) {
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 做一个简单的小结。
NativeModules
(NativeModules == global.nativeModuleProxy == native 的 NativeModuleProxy)调用 native 侧的getModule->createModule
直到调用到js侧的__fbGenNativeModule
也就是 js 侧的getModule
函数;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 是如何交互的。
RCTJavaScriptDidLoadNotification
时间给 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
NativeModules
(NativeModules == global.nativeModuleProxy == native 的 NativeModuleProxy)调用 native 侧的getModule->createModule
直到调用到js侧的__fbGenNativeModule
也就是 js 侧的getModule
函数;getModule
函数会返回当前 Module 的信息给 native,并将当前的 moduleId,methodId 已经 params 塞入队列;通过对比上下两次请求的时间间隔是否>5ms,则会利用nativeFlushQueueImmediate
立即调用native modules
.ReactNative 与 iOS 原生通信原理解析系列