上一期我们介绍ReactNative IOS 运行原理之<OC TO JavaScript>的大致运行原理,这一期我们继续讲JavaScript TO OC的运行原理,再顺便讲讲上篇文章中遗留的一个问题,OK开始吧:
首先上一个图:
其实这个图已经讲解得很透彻了,我只是在随着加上些自己的东西吧,
那么我们先来看看在 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_)));
}
这里面会用OC对象nativeModules_来填充JS端的NativeModuleProxy对象其实就是:native端nativeModules_ == JS端nativeModuleProxy == JS端NativeModules == global.nativeModuleProxy
我看了很多文章里面都说在 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 再执行这个队列里要调用的方法。
一般网上都是这个调用步骤,我感觉与我的调试情况来看有点不一样, 我说一下我的调试发现的调用步骤是怎么样的:
let NativeModules: {[moduleName: string]: Object, ...} = {};
if (global.nativeModuleProxy) {
NativeModules = global.nativeModuleProxy;
} else if (!global.nativeExtensions) {
const bridgeConfig = global.__fbBatchedBridgeConfig;
invariant(
bridgeConfig,
'__fbBatchedBridgeConfig is not set, cannot invoke native modules',
);
const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty');
(bridgeConfig.remoteModuleConfig || []).forEach(
(config: ModuleConfig, moduleID: number) => {
// Initially this config will only contain the module name when running in JSC. The actual
// configuration of the module will be lazily loaded.
const info = genModule(config, moduleID);
if (!info) {
return;
}
if (info.module) {
NativeModules[info.name] = info.module;
}
// If there's no module config, define a lazy getter
else {
defineLazyObjectProperty(NativeModules, info.name, {
get: () => loadModule(info.name, moduleID),
});
}
},
);
}
我调试过程中发现运行的是上面代码中的else部分(网上大部分说法都是运行if语句的表达式触发调用的),__fbBatchedBridgeConfig 是OC通过 JavaScriptCore注入的关于Native Module与Method的config配置清单
folly::dynamic nativeModuleConfig = folly::dynamic::array;
auto moduleRegistry = m_delegate->getModuleRegistry();
for (const auto &name : moduleRegistry->moduleNames()) {
auto config = moduleRegistry->getConfig(name);
nativeModuleConfig.push_back(config ? config->config : nullptr);
}
folly::dynamic config = folly::dynamic::object("remoteModuleConfig", std::move(nativeModuleConfig));
setGlobalVariable("__fbBatchedBridgeConfig", std::make_unique<JSBigStdString>(folly::toJson(config)));
展开如下图:
这里面会运行forEach方法挨个遍历里面的每一个元素config去调用genModule方法,(我调试的时候是这里才会触发genModule方法)在这个方法里面,
genModule里面又会对于methods所有方法的遍历调用genMethod方法去获取每一个module[methodName]的具体方法的实现
//NativeModules.js
//function genModule 省略部分
//genModule方法以上省略
const module = {};
methods &&
methods.forEach((methodName, methodID) => {
const isPromise =
promiseMethods && arrayContains(promiseMethods, methodID);
const isSync = syncMethods && arrayContains(syncMethods, methodID);
invariant(
!isPromise || !isSync,
'Cannot have a method that is both async and a sync hook',
);
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
module[methodName] = genMethod(moduleID, methodID, methodType);
});
//genModule方法以下省略
function genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = null;
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';
hasErrorCallback &&
invariant(
hasSuccessCallback,
'Cannot have a non-function arg after a function arg.',
);
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;
}
这个里面会返回一个FN,说白了就是你的模块里面的方法生成一个函数实现,这个实现是统一的格式就这么简单:
return BatchedBridge.callNativeSyncHook(
moduleID,
methodID,
args,
onFail,
onSuccess,
);
注意上面图片的第一个红框表示了 “addHelloWord” 这个函数的实现是就是上面代码段里面的 “function nonPromiseMethodWrapper(...args: Array<any>)”
的部分,第二个红框表示的是把初始化的整个module赋值给了NativeModules[info.name] 的这个module,以便于JavaScript端来调用这个NativeModules[info.name]里面的函数
然后通过下面这句赋值把NativeModules里面的元素(对应每一个原生模块)赋值给他,他就拥有的函数属性的实现
if (info.module) {
NativeModules[info.name] = info.module;
}
enqueueNativeCall(
moduleID: number,
methodID: number,
params: any[],
onFail: ?Function,
onSucc: ?Function,
){
//enqueueNativeCall函数的部分实现
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);
}
这个nativeFlushQueueImmediate就是上文中介绍的通过JavaScriptCore与OC绑定的函数,为了大家阅读方便我再贴一遍:
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();
}));
里面通过callNativeModules函数利用runtime来实现OC代码的调用,大致这个过程就是这样了,对于上文中提到的与其他博客不一样的调用步骤如果有知道原因的可以私信一下发给我
还记得上一篇文章提到的一个疑问吗我们在分析OC TO JavaScript的时候发现在MessageQueue.js 里面调用 callFunctionReturnFlushedQueue 执行的时候还执行了 return this.flushedQueue(); 去返回一个队列给Native端,这个队列有个什么作用呢?
flushedQueue(): null | [Array<number>, Array<number>, Array<any>, number] {
this.__guard(() => {
this.__callImmediates();
});
const queue = this._queue;
this._queue = [[], [], [], this._callID];
return queue[0].length ? queue : null;
}
其实这个_queue队列就是每次Javascript TO OC的时候进入的队列
enqueueNativeCall(
moduleID: number,
methodID: number,
params: any[],
onFail: ?Function,
onSucc: ?Function,
) {
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
this._queue[PARAMS].push(params);
//以下部分省略
enqueueNativeCall上面我们分析了就是每次Javascript TO OC的调用函数,这个函数其实会把调的Module,Method,Param传到这个_queue里面,有什么作用呢,在仔细看这个函数的后半部分:
//enqueueNativeCall函数
const now = Date.now();
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);
}
这个判断是说当前与上次_lastFlush时间如果大于MIN_TIME_BETWEEN_FLUSHES_MS(5毫秒)的时候才会立即调用nativeFlushQueueImmediate函数去触发OC的方法,如果小于5毫秒的时候只会把这些参数入队列,等待有人来取队列
不知道你反应过来了没有,我们OC TO Javascript的时候返回值就会去取这个队列作为返回值再根据里面的Module,Method,Param调用具体的OC方法,如下(还记得吧):
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);
}
callNativeModules函数上面也分析过了利用runtime来实现OC代码的调用
为什么会有这种奇怪方式的调用呢,我查询了目前很多博客的观点大致都是这样的:
JS不会主动传递数据给OC,在调OC方法时,会把ModuleID,MethodID等数据加到一个队列里,等OC过来调JS的任意方法时,再把这个队列返回给OC,此时OC再执行这个队列里要调用的方法。让我们回顾下iOS的事件传递和响应机制就会恍然大悟,在Native开发中,只在有事件触发的时候,才会调用native代码。这个事件可以是启动事件、触摸事件、滚动事件、timer事件、系统事件、回调事件。而在React Native里,本质上JSX Component最终都是native view。这些事件发生时OC都会调用JS侧相应的方法以保证JS侧和native侧保持同步,JS处理完这些事件后再执行JS想让OC执行的方法,而没有事件发生的时候,是不会执行任何代码的,这跟native开发里事件响应机制是一致的。在native调用JS的时候,JS把需要native处理的方法队列返回给native侧让native去处理。这样既保证了JS和native侧事件和逻辑的同步,JS也可以趁机搭车call native,避免了JS和native侧频繁的通信。
这种解释也很合理大意就是OC TO Javascript的时候会把Javascript TO OC的方法及参数返回避免了JS和native侧频繁的通信,但是我一细想还是有点问题,你想啊,OC TO Javascript的时候获取队列_queue,但是这个之前你要把你想调用的Module,Method,Param入队列才行啊,所以应该还是要先执行JavaScript TO OC调用入队列的操作才行,我能想到的就是在某种情况下比如OC 与 Javascript 相互调用比较频繁的时候,OC TO Javascript来取上一次Javascript TO OC的队列的参数然后执行OC端的方法
不知道这种猜想是否正确,有知道或者感兴趣的小伙伴可以在下面留言一起讨论下
好了Javascript TO OC部分就到这里了,欢迎大家给我留言···