文章首发集体博客: 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 办法,办法内调用了 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 原生通信原理解析系列
- ReactNative 与 iOS 原生通信原理解析 - 初始化
- ReactNative 与 iOS 原生通信原理解析 -JS 加载及执行篇
- ReactNative 与 iOS 原生通信原理解析 - 通信篇