这一期我们来介绍下ReactNative IOS 运行原理之<OC TO JavaScript>,老规矩我们不做每个细节的介绍,我们只介绍调用的实现原理,首先介绍OC TO JavaScript端的调用过程:
1 . OC TO JavaScript 首先先上图大致流程:
我们不做过多的介绍只介绍说红框的部分,也就是OC TO JavaScript的主要调用部分
image.png- (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];
}
enqueueJSCall就是OC TO JavaScript的调用,大家先记住这个函数,首先我们从头开始,首先初始化的时候 JSIExecutor 中执行 flush 函数;flush 函数中会在首次时对 JS 和 native 进行绑定;在绑定之后 native 就可以调用 JS 函数,实现 OC TO JavaScript 之间的通信。绑定规则如下:
// 各种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");
});
}
__fbBatchedBridge 其实就是一个全局的 MessageQueue对象
在BatchedBridge.js里面如下:
const MessageQueue = require('./MessageQueue');
const BatchedBridge: MessageQueue = new MessageQueue();
// Wire up the batched bridge on the global object so that we can call into it.
// Ideally, this would be the inverse relationship. I.e. the native environment
// provides this global directly with its script embedded. Then this module
// would export it. A possible fix would be to trim the dependencies in
// MessageQueue to its minimal features and embed that in the native runtime.
Object.defineProperty(global, '__fbBatchedBridge', {
configurable: true,
value: BatchedBridge,
});
module.exports = BatchedBridge;
其中绑定的时候 batchedBridge.getPropertyAsFunction 是对于JavaScriptCore函数的封装而已,本质上还是调用了JavaScriptCore的API来实现的,这里是获取到了JS端的callFunctionReturnFlushedQueue函数指针保存到了callFunctionReturnFlushedQueue_里面准备进行OC TO JavaScript的调用
getPropertyAsFunctiond 方法的调用介绍如下:
Function Object::getPropertyAsFunction(Runtime& runtime, const char* name)
const {
Object obj = getPropertyAsObject(runtime, name);
if (!obj.isFunction(runtime)) {
throw JSError(
runtime,
std::string("getPropertyAsFunction: property '") + name + "' is " +
kindToString(std::move(obj), &runtime) + ", expected a Function");
};
return std::move(obj).getFunction(runtime);
}
Object Object::getPropertyAsObject(Runtime& runtime, const char* name) const {
Value v = getProperty(runtime, name);
if (!v.isObject()) {
throw JSError(
runtime,
std::string("getPropertyAsObject: property '") + name + "' is " +
kindToString(v, &runtime) + ", expected an Object");
}
return v.getObject(runtime);
}
inline Value Object::getProperty(Runtime& runtime, const char* name) const {
return getProperty(runtime, String::createFromAscii(runtime, name));
}
inline Value Object::getProperty(Runtime& runtime, const String& name) const {
return runtime.getProperty(*this, name);
}
// 最终调用了这个JavaScriptCore API的这个函数 JSObjectGetProperty
jsi::Value JSCRuntime::getProperty(
const jsi::Object &obj,
const jsi::String &name) {
JSObjectRef objRef = objectRef(obj);
JSValueRef exc = nullptr;
JSValueRef res = JSObjectGetProperty(ctx_, objRef, stringRef(name), &exc);
checkException(exc);
return createValue(res);
}
最终调用了这个JavaScriptCore API的这个函数 JSObjectGetProperty 去获取到JS代码的函数映射到OC代码里面,用来作为OC函数触发调用,关于JavaScriptCore的介绍我们打算再分一期来重点介绍这部分的内容
对于 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);
}
最终掉用到了 MessageQueue.js 里面的 callFunctionReturnFlushedQueue函数,里面使用JavaScript的apply语法来执行这个模块方法的调用
moduleMethods[method].apply(moduleMethods, args); 来执行JS函数
其中在AppRegistry.js里面对于这个JS模块进行了注册
//AppRegistry.js
BatchedBridge.registerCallableModule(‘AppRegistry’, AppRegistry);
MessageQueue.js 里面保存到了_lazyCallableModules数组里面
//MessageQueue.js
registerCallableModule(name: string, module: Object) {
this._lazyCallableModules[name] = () => module;
}
__callFunction里面的 this.getCallableModule实际上就是按照名字取出了这个模块,然后进行调用
//MessageQueue.js
getCallableModule(name: string): any | null {
const getValue = this._lazyCallableModules[name];
return getValue ? getValue() : null;
}
__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);
Systrace.endEvent();
}
这样就完成了OC TO JavaScript的调用,总结如果OC TO JavaScript端的调用的话总体来说源头就是enqueueJSCall方法:
// RCTxxBridge.mm
- (void)enqueueJSCall:(NSString *)module
method:(NSString *)method
args:(NSArray *)args
completion:(dispatch_block_t)
我们来总结一下调用流程:
-
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 的调用。
值得一提的是官方推荐OC TO JavaScript使用RCTEventEmitter来实现,首先继承自他,实现如下:
// CalendarManager.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarManager : RCTEventEmitter <RCTBridgeModule>
@end
然后通过 sendEventWithName 触发调用,其中这里面也是用到了enqueueJSCall:这个方法,大家可以细细回味一下,是不是很多东西都有共通性呢?
- (void)sendEventWithName:(NSString *)eventName body:(id)body
{
RCTAssert(
_bridge != nil || _invokeJS != nil,
@"Error when sending event: %@ with body: %@. "
"Bridge is not set. This is probably because you've "
"explicitly synthesized the bridge in %@, even though it's inherited "
"from RCTEventEmitter.",
eventName,
body,
[self class]);
if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
RCTLogError(
@"`%@` is not a supported event type for %@. Supported events are: `%@`",
eventName,
[self class],
[[self supportedEvents] componentsJoinedByString:@"`, `"]);
}
if (_listenerCount > 0 && _bridge) {
[_bridge enqueueJSCall:@"RCTDeviceEventEmitter"
method:@"emit"
args:body ? @[ eventName, body ] : @[ eventName ]
completion:NULL];
} else if (_listenerCount > 0 && _invokeJS) {
_invokeJS(@"RCTDeviceEventEmitter", @"emit", body ? @[ eventName, body ] : @[ eventName ]);
} else {
RCTLogWarn(@"Sending `%@` with no listeners registered.", eventName);
}
}
细心的你应该发现一个问题就是 callFunctionReturnFlushedQueue 执行的时候还执行了 return this.flushedQueue(); 去返回一个队列给Native端,这个队列有个什么作用呢,等下节我们讲到Javascript TO OC的时候为大家揭晓答案
好了OC TO JavaScript部分就到这里了,下一篇介绍JavaScript TO OC的调用原理···
网友评论