文章首发集体博客: 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.cppvoid 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.cppvoid 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.cppvoid 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 :ReactJSBridgeconst 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.cppvoid NativeToJsBridge::initializeRuntime() { runOnExecutorQueue( [](JSExecutor *executor) mutable { executor->initializeRuntime(); });}// JSIExecutor.cppvoid 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.cppValue 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.jsfunction 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 nativeglobal.__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.cppvoid 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 原生通信原理解析-通信篇