导语:其实本来是想编写一篇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 原生通讯原理解析系列