導語:其實本來是想編寫一篇react-native
(下文簡稱 rn) 在iOS
中如何實現jsbridge
的文章;相信看過官方文檔的同窗都清楚 rn 和 iOS 通訊使用了一個叫RCTBridgeModule
的模塊去實現;相信你們與我同樣,不能知其然不知其因此然;因此決定去翻一番 rn 的源碼,一探其 rn 與 iOS 通訊的機制。結果隨着分析的深刻發現內容較多;因而編寫了 ReactNative 與 iOS 原生通訊原理解析-初始化 和 ReactNative 與 iOS 原生通訊原理解析-JS 加載及執行篇 兩篇 RN 源碼分析文章。
本文將在上述兩篇文章的基礎上,繼續深刻理解 RN 與 iOS 原生通訊機制。javascript
聲明: 本文所使用的 rn 版本爲0.63.0
。html
看過前面一篇ReactNative 與 iOS 原生通訊原理解析-JS 加載及執行篇 的同窗應該已經清楚,在執行完成 js 代碼以後,會在 JSIExecutor
中執行 flush 函數;flush 函數中會在首次時對 JS 和 native
進行綁定;在綁定以後 native
就能夠調用 JS 函數,實現 native to js 之間的通訊。java
// 各類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 函數的呢?下面咱們一塊兒來了解。react
不知您是否還記得,native 在執行 js 代碼的時候,有一個回調函數,函數內部經過事件的方式通知 RCTRootView javascript 已經加載。git
// 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
事件;並執行以下方法:github
(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_
方法。react-native
在 bindBridge 中callFunctionReturnFlushedQueue_
是經過 runtime 的方式將 native 的callFunctionReturnFlushedQueue_
指向了 js 中的callFunctionReturnFlushedQueue
函數的。promise
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
是相似的。
流程圖請見文末!
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 作一個簡單的小結。
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 是如何交互的。
Native to JS
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 原生通訊原理解析系列