RN:ReactNative 简称,文章中后续使用RN进行代替全称
NA:Native的简称
本文是RN(iOS)通信分析的开篇,在应用例子使用,JS/OC/C++ 代码上做一个整体串联。来帮助大家先简单了解下RN通信是如何Work的,后续会不断的在通信纬度做更多的源码分析,带大家更加清楚的认识RN。
1. JavaScript Native 是如何建立连的
确保 JavaScript (JS) 和 Native 之间的顺畅通信和内容共享,通常依赖于 JS 引擎库的强大功能。这些库,如 V8 引擎,可以向 JS 环境注入方法和对象,从而实现双向交流。在 RN 中,这一过程通过一个特别的层——JavaScript Interface (JSI) 实现。JSI 为 JS 引擎和 Native (C++) 之间的互动提供了封装,使得双向调用变得简单。
JSI 通过一个称为 HostObject 的接口实现双向映射,官方也将其描述为一个映射框架。这个机制不仅简化了复杂的底层通信,还允许开发者以更直观的方式在 JS 和 Native 之间完成数据传递的功能。通过这种架构,RN 能够提供一个高效且灵活的开发环境,适用于快速迭代和跨平台的应用开发需求。
(推荐一篇写的不错的文章介绍JSI)
下面是截取一段RN中的代码来带大家认识下RN中的双向绑定支持:
NA注册提供JS调用
截取自:JSIExecutor.cpp 文件
void JSIExecutor::initializeRuntime() {
bindNativePerformanceNow(*runtime_);
runtime_->global().setProperty(
*runtime_,
"nativeCallSyncHook",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
1,
[this](
jsi::Runtime&,
const jsi::Value&,
const jsi::Value* args,
size_t count) { return nativeCallSyncHook(args, count); }));
}
上述代码中在JS-Global中注入了一个属性,名字为nativeCallSyncHook的native方法, setProperty。以供JS侧能够直接调用该方法;
截取自:MessageQueue.js
callNativeSyncHook(
moduleID: number,
methodID: number,
params: mixed[],
onFail: ?(...mixed[]) => void,
onSucc: ?(...mixed[]) => void,
): mixed {
if (__DEV__) {
invariant(
global.nativeCallSyncHook,
'Calling synchronous methods on native ' +
'modules is not supported in Chrome.\n\n Consider providing alternative ' +
'methods to expose this method in debug mode, e.g. by exposing constants ' +
'ahead-of-time.',
);
}
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
return global.nativeCallSyncHook(moduleID, methodID, params);
}
在JS侧调用的时候直接对 global 进行方法调用,global.nativeCallSyncHook(moduleID, methodID, params)。
这一段代码已经完成了JS调用NA的处理。
JS注册提供NA调用
截取自:MessageQueue.js
constructor() {
// $FlowFixMe[cannot-write]
this.callFunctionReturnFlushedQueue =
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this.callFunctionReturnFlushedQueue.bind(this);
}
callFunctionReturnFlushedQueue(
module: string,
method: string,
args: mixed[],
): null | [Array<number>, Array<number>, Array<mixed>, number] {
this.__guard(() => {
this.__callFunction(module, method, args);
});
return this.flushedQueue();
}
完成bind函数 callFunctionReturnFlushedQueue 。
截取自:JSIExecutor.cpp 文件
void JSIExecutor::bindBridge() {
std::call_once(bindFlag_, [this] {
Value batchedBridgeValue =
runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
*runtime_, "callFunctionReturnFlushedQueue");
});
}
NA根据JS上下问获取到JS属性,完成NA调用JS的链路。
上面介绍提及的 global/bind 等来自于JS,自行查阅详细内容~
核心实现在JS中注入对应属性,根据引擎提供的上下文能力获取对应的属性。
利用JS代码的特性,JSI提供的HostObject接口能力。双边完成映射后,我们就能在链路上完成JS-NA的相互调用。接下来我们详细看下,一次完整的调用方法是如何流转的。
2. JS -> NA 方法调用
- 调用NativeModule已经完成注册的对应Module,方法
const {MyNativeModule} = NativeModules;
MyNativeModule.showNativeAlert(
'Hello from React Native!',
(error, result) => {
if (error) {
console.error(error);
} else {
console.log(result);
}
},
);
- 寻找到对应的 moduleId,methodId,完成参数传递调用方法 (BatchedBridge.enqueueNativeCall)
function genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = null;
if (type === 'promise') {
fn = function promiseMethodWrapper(...args: Array<mixed>) {
// In case we reject, capture a useful stack trace here.
/* $FlowFixMe[class-object-subtyping] added when improving typing for
* this parameters */
const enqueueingFrameError: ExtendedError = new Error();
return new Promise((resolve, reject) => {
// 真实调用 nativeCall
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
data => resolve(data),
errorData =>
reject(
updateErrorWithErrorData(
(errorData: $FlowFixMe),
enqueueingFrameError,
),
),
);
});
};
} else {
fn = function nonPromiseMethodWrapper(...args: Array<mixed>) {
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.',
);
// $FlowFixMe[incompatible-type]
const onSuccess: ?(mixed) => void = hasSuccessCallback ? lastArg : null;
// $FlowFixMe[incompatible-type]
const onFail: ?(mixed) => void = hasErrorCallback ? secondLastArg : null;
// $FlowFixMe[unsafe-addition]
const callbackCount = hasSuccessCallback + hasErrorCallback;
const newArgs = args.slice(0, args.length - callbackCount);
if (type === 'sync') {
// 真实调用 nativeCall
return BatchedBridge.callNativeSyncHook(
moduleID,
methodID,
newArgs,
onFail,
onSuccess,
);
} else {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
newArgs,
onFail,
onSuccess,
);
}
};
}
// $FlowFixMe[prop-missing]
fn.type = type;
return fn;
}
- BatchedBridge 其实就是MessageQueue
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;
- MessageQueue global.nativeFlushQueueImmediate(queue); 执行C++注入的方法
enqueueNativeCall(
moduleID: number,
methodID: number,
params: mixed[],
onFail: ?(...mixed[]) => void,
onSucc: ?(...mixed[]) => void,
): void {
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
// Replacement allows normally non-JSON-convertible values to be
// seen. There is ambiguity with string values, but in context,
// it should at least be a strong hint.
const replacer = (key: string, val: $FlowFixMe) => {
const t = typeof val;
if (t === 'function') {
return '<<Function ' + val.name + '>>';
} else if (t === 'number' && !isFinite(val)) {
return '<<' + val.toString() + '>>';
} else {
return val;
}
};
// Note that JSON.stringify
invariant(
isValidArgument(params),
'%s is not usable as a native method argument',
JSON.stringify(params, replacer),
);
// The params object should not be mutated after being queued
deepFreezeAndThrowOnMutationInDev(params);
}
this._queue[PARAMS].push(params);
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);
}
}
- JSIExecutor , 执行 C++ 注入的函数,后续执行 callNativeModules 方法
runtime_->global().setProperty(
*runtime_,
"nativeFlushQueueImmediate",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
1,
[this](
jsi::Runtime& 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();
}));
执行 delegate_ -> callNativeModules(
this, dynamicFromValue(runtime_, queue), isEndOfBatch); 根据runtime上下文解析传递Calls,实际由JsToNativeBridge 执行逻辑
std::shared_ptr<ExecutorDelegate> delegate_;
void JSIExecutor::callNativeModules(const Value& queue, bool isEndOfBatch) {
SystraceSection s("JSIExecutor::callNativeModules");
// If this fails, you need to pass a fully functional delegate with a
// module registry to the factory/ctor.
CHECK(delegate_) << "Attempting to use native modules without a delegate";
#if 0 // maybe useful for debugging
std::string json = runtime_->global().getPropertyAsObject(*runtime_, "JSON")
.getPropertyAsFunction(*runtime_, "stringify").call(*runtime_, queue)
.getString(*runtime_).utf8(*runtime_);
#endif
BridgeNativeModulePerfLogger::asyncMethodCallBatchPreprocessStart();
delegate_->callNativeModules(
*this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}
- JsToNativeBridge 解析 std::vector<MethodCall> methodCalls = parseMethodCalls(std::move(calls));,到对应函数表中 m_registry 寻找对应id的方法映射
std::shared_ptr<ModuleRegistry> m_registry;
void callNativeModules(
[[maybe_unused]] JSExecutor& executor,
folly::dynamic&& calls,
bool isEndOfBatch) override {
CHECK(m_registry || calls.empty())
<< "native module calls cannot be completed with no native modules";
m_batchHadNativeModuleOrTurboModuleCalls =
m_batchHadNativeModuleOrTurboModuleCalls || !calls.empty();
std::vector<MethodCall> methodCalls = parseMethodCalls(std::move(calls));
BridgeNativeModulePerfLogger::asyncMethodCallBatchPreprocessEnd(
(int)methodCalls.size());
// An exception anywhere in here stops processing of the batch. This
// was the behavior of the Android bridge, and since exception handling
// terminates the whole bridge, there's not much point in continuing.
for (auto& call : methodCalls) {
m_registry->callNativeMethod(
call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
if (isEndOfBatch) {
// onBatchComplete will be called on the native (module) queue, but
// decrementPendingJSCalls will be called sync. Be aware that the bridge
// may still be processing native calls when the bridge idle signaler
// fires.
if (m_batchHadNativeModuleOrTurboModuleCalls) {
m_callback->onBatchComplete();
m_batchHadNativeModuleOrTurboModuleCalls = false;
}
m_callback->decrementPendingJSCalls();
}
}
- ModuleRegistry NativeModule->invoke(methodId, std::move(params), callId); 对应Module执行MethodId对应的方法
std::vector<std::unique_ptr<NativeModule>> modules_;
void ModuleRegistry::callNativeMethod(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& params,
int callId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(folly::to<std::string>(
"moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
}
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
- RCTNativeModule
- invokeInner(weakBridge, weakModuleData, methodId, std::move(params), callId, isSyncModule ? Sync : Async); 方法进行invoke
- id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
- 最终透传到 RCTModuleMethod 中进行处理
void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &¶ms, int callId)
{
id<RCTBridgeMethod> method = m_moduleData.methods[methodId];
if (method) {
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways,
@"[RCTNativeModule invoke]",
@{@"method" : [NSString stringWithUTF8String:method.JSMethodName]});
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
const char *moduleName = [m_moduleData.name UTF8String];
const char *methodName = m_moduleData.methods[methodId].JSMethodName;
dispatch_queue_t queue = m_moduleData.methodQueue;
const bool isSyncModule = queue == RCTJSThread;
if (isSyncModule) {
BridgeNativeModulePerfLogger::syncMethodCallStart(moduleName, methodName);
BridgeNativeModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallStart(moduleName, methodName);
}
// capture by weak pointer so that we can safely use these variables in a callback
__weak RCTBridge *weakBridge = m_bridge;
__weak RCTModuleData *weakModuleData = m_moduleData;
// The BatchedBridge version of this buckets all the callbacks by thread, and
// queues one block on each. This is much simpler; we'll see how it goes and
// iterate.
dispatch_block_t block = [weakBridge, weakModuleData, methodId, params = std::move(params), callId, isSyncModule] {
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
}
#else
(void)(callId);
#endif
@autoreleasepool {
invokeInner(weakBridge, weakModuleData, methodId, std::move(params), callId, isSyncModule ? Sync : Async);
}
};
if (isSyncModule) {
block();
BridgeNativeModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName);
} else if (queue) {
BridgeNativeModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
dispatch_async(queue, block);
}
if (isSyncModule) {
BridgeNativeModulePerfLogger::syncMethodCallEnd(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallEnd(moduleName, methodName);
}
}
static MethodCallResult invokeInner(
RCTBridge *bridge,
RCTModuleData *moduleData,
unsigned int methodId,
const folly::dynamic ¶ms,
int callId,
SchedulingContext context)
{
if (!bridge || !bridge.valid || !moduleData) {
if (context == Sync) {
/**
* NOTE: moduleName and methodName are "". This shouldn't be an issue because there can only be one ongoing sync
* call at a time, and when we call syncMethodCallFail, that one call should terminate. This is also an
* exceptional scenario, so it shouldn't occur often.
*/
BridgeNativeModulePerfLogger::syncMethodCallFail("N/A", "N/A");
}
return std::nullopt;
}
id<RCTBridgeMethod> method = moduleData.methods[methodId];
if (RCT_DEBUG && !method) {
RCTLogError(@"Unknown methodID: %ud for module: %@", methodId, moduleData.name);
}
const char *moduleName = [moduleData.name UTF8String];
const char *methodName = moduleData.methods[methodId].JSMethodName;
if (context == Async) {
BridgeNativeModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodName, (int32_t)callId);
BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionStart(moduleName, methodName, (int32_t)callId);
}
NSArray *objcParams = convertFollyDynamicToId(params);
if (context == Sync) {
BridgeNativeModulePerfLogger::syncMethodCallArgConversionEnd(moduleName, methodName);
}
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways,
@"[RCTNativeModule invokeInner]",
@{@"method" : [NSString stringWithUTF8String:method.JSMethodName]});
@try {
if (context == Sync) {
BridgeNativeModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionEnd(moduleName, methodName, (int32_t)callId);
}
id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
if (context == Sync) {
BridgeNativeModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodName);
BridgeNativeModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodName, (int32_t)callId);
}
return convertIdToFollyDynamic(result);
} @catch (NSException *exception) {
if (context == Sync) {
BridgeNativeModulePerfLogger::syncMethodCallFail(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallExecutionFail(moduleName, methodName, (int32_t)callId);
}
// Pass on JS exceptions
if ([exception.name hasPrefix:RCTFatalExceptionName]) {
@throw exception;
}
} @finally {
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
return std::nullopt;
}
- RCTModuleMethod
- processMethodSignature 完成方法签名,转发到对应 _argumentBlocks 中
- [bridge enqueueCallback:json args:@[ RCTJSErrorFromNSError(error) ]]; 发现都是在调用bridge enqueueCallback 方法。
- bridge 是RCTBridge,所以接下来看 RCTBridge.enqueueCallback
- (void)processMethodSignature
{
NSArray<RCTMethodArgument *> *arguments;
_selector = NSSelectorFromString(RCTParseMethodSignature(_methodInfo->objcName, &arguments));
RCTAssert(_selector, @"%s is not a valid selector", _methodInfo->objcName);
// Create method invocation
NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
RCTAssert(methodSignature, @"%s is not a recognized Objective-C method.", sel_getName(_selector));
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = _selector;
_invocation = invocation;
NSMutableArray *retainedObjects = [NSMutableArray array];
_retainedObjects = retainedObjects;
// Process arguments
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
NSMutableArray<RCTArgumentBlock> *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
#if RCT_DEBUG
__weak RCTModuleMethod *weakSelf = self;
#endif
#define RCT_RETAINED_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(__unused __weak RCTBridge * bridge, NSUInteger index, id json) { \
_logic [invocation setArgument:&value atIndex:(index) + 2]; \
if (value) { \
[retainedObjects addObject:value]; \
} \
return YES; \
}]
#define __PRIMITIVE_CASE(_type, _nullable) \
{ \
isNullableType = _nullable; \
_type (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend; \
[argumentBlocks addObject:^(__unused RCTBridge * bridge, NSUInteger index, id json) { \
_type value = convert([RCTConvert class], selector, json); \
[invocation setArgument:&value atIndex:(index) + 2]; \
return YES; \
}]; \
break; \
}
#define PRIMITIVE_CASE(_type) __PRIMITIVE_CASE(_type, NO)
#define NULLABLE_PRIMITIVE_CASE(_type) __PRIMITIVE_CASE(_type, YES)
// Explicitly copy the block
#define __COPY_BLOCK(block...) \
id value = [block copy]; \
if (value) { \
[retainedObjects addObject:value]; \
}
#if RCT_DEBUG
#define BLOCK_CASE(_block_args, _block) \
RCT_RETAINED_ARG_BLOCK(if (json && ![json isKindOfClass:[NSNumber class]]) { \
RCTLogArgumentError(weakSelf, index, json, "should be a function"); \
return NO; \
} __block BOOL didInvoke = NO; \
__COPY_BLOCK(^_block_args { \
if (checkCallbackMultipleInvocations(&didInvoke)) \
_block \
});)
#else
#define BLOCK_CASE(_block_args, _block) \
RCT_RETAINED_ARG_BLOCK(__COPY_BLOCK(^_block_args{ \
_block});)
#endif
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
BOOL isNullableType = NO;
RCTMethodArgument *argument = arguments[i - 2];
NSString *typeName = argument.type;
NSLog(@"typeName: %@", typeName);
SEL selector = selectorForType(typeName);
if ([RCTConvert respondsToSelector:selector]) {
switch (objcType[0]) {
// Primitives
case _C_CHR:
PRIMITIVE_CASE(char)
case _C_UCHR:
PRIMITIVE_CASE(unsigned char)
case _C_SHT:
PRIMITIVE_CASE(short)
case _C_USHT:
PRIMITIVE_CASE(unsigned short)
case _C_INT:
PRIMITIVE_CASE(int)
case _C_UINT:
PRIMITIVE_CASE(unsigned int)
case _C_LNG:
PRIMITIVE_CASE(long)
case _C_ULNG:
PRIMITIVE_CASE(unsigned long)
case _C_LNG_LNG:
PRIMITIVE_CASE(long long)
case _C_ULNG_LNG:
PRIMITIVE_CASE(unsigned long long)
case _C_FLT:
PRIMITIVE_CASE(float)
case _C_DBL:
PRIMITIVE_CASE(double)
case _C_BOOL:
PRIMITIVE_CASE(BOOL)
case _C_SEL:
NULLABLE_PRIMITIVE_CASE(SEL)
case _C_CHARPTR:
NULLABLE_PRIMITIVE_CASE(const char *)
case _C_PTR:
NULLABLE_PRIMITIVE_CASE(void *)
case _C_ID: {
isNullableType = YES;
id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCT_RETAINED_ARG_BLOCK(id value = convert([RCTConvert class], selector, json););
break;
}
case _C_STRUCT_B: {
NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector];
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = selector;
typeInvocation.target = [RCTConvert class];
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) {
void *returnValue = malloc(typeSignature.methodReturnLength);
if (!returnValue) {
// CWE - 391 : Unchecked error condition
// https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
// https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
abort();
}
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:returnValue];
[invocation setArgument:returnValue atIndex:index + 2];
free(returnValue);
return YES;
}];
break;
}
default: {
static const char *blockType = @encode(__typeof__(^{
}));
if (!strcmp(objcType, blockType)) {
BLOCK_CASE((NSArray * args), { [bridge enqueueCallback:json args:args]; });
} else {
RCTLogError(@"Unsupported argument type '%@' in method %@.", typeName, [self methodName]);
}
}
}
} else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) {
BLOCK_CASE((NSArray * args), { [bridge enqueueCallback:json args:args]; });
} else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) {
BLOCK_CASE((NSError * error), { [bridge enqueueCallback:json args:@[ RCTJSErrorFromNSError(error) ]]; });
} else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
RCTAssert(
i == numberOfArguments - 2,
@"The RCTPromiseResolveBlock must be the second to last parameter in %@",
[self methodName]);
BLOCK_CASE((id result), { [bridge enqueueCallback:json args:result ? @[ result ] : @[]]; });
} else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
RCTAssert(
i == numberOfArguments - 1, @"The RCTPromiseRejectBlock must be the last parameter in %@", [self methodName]);
BLOCK_CASE((NSString * code, NSString * message, NSError * error), {
NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
[bridge enqueueCallback:json args:@[ errorJSON ]];
});
} else if ([typeName hasPrefix:@"JS::"]) {
NSString *selectorNameForCxxType =
[[typeName stringByReplacingOccurrencesOfString:@"::" withString:@"_"] stringByAppendingString:@":"];
selector = NSSelectorFromString(selectorNameForCxxType);
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) {
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], selector, json);
void *pointer = box.voidPointer;
[invocation setArgument:&pointer atIndex:index + 2];
[retainedObjects addObject:box];
return YES;
}];
} else {
// Unknown argument type
RCTLogError(
@"Unknown argument type '%@' in method %@. Extend RCTConvert to support this type.",
typeName,
[self methodName]);
}
#if RCT_DEBUG
RCTNullability nullability = argument.nullability;
if (!isNullableType) {
if (nullability == RCTNullable) {
RCTLogArgumentError(
weakSelf,
i - 2,
typeName,
"is marked as "
"nullable, but is not a nullable type.");
}
nullability = RCTNonnullable;
}
/**
* Special case - Numbers are not nullable in Android, so we
* don't support this for now. In future we may allow it.
*/
if ([typeName isEqualToString:@"NSNumber"]) {
BOOL unspecified = (nullability == RCTNullabilityUnspecified);
if (!argument.unused && (nullability == RCTNullable || unspecified)) {
RCTLogArgumentError(
weakSelf,
i - 2,
typeName,
[unspecified ? @"has unspecified nullability" : @"is marked as nullable"
stringByAppendingString:@" but React requires that all NSNumber "
"arguments are explicitly marked as `nonnull` to ensure "
"compatibility with Android."]
.UTF8String);
}
nullability = RCTNonnullable;
}
if (nullability == RCTNonnullable) {
RCTArgumentBlock oldBlock = argumentBlocks[i - 2];
argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) {
if (json != nil) {
if (!oldBlock(bridge, index, json)) {
return NO;
}
if (isNullableType) {
// Check converted value wasn't null either, as method probably
// won't gracefully handle a nil value for a nonull argument
void *value;
[invocation getArgument:&value atIndex:index + 2];
if (value == NULL) {
return NO;
}
}
return YES;
}
RCTLogArgumentError(weakSelf, index, typeName, "must not be null");
return NO;
};
}
#endif
}
#if RCT_DEBUG
const char *objcType = _invocation.methodSignature.methodReturnType;
if (_methodInfo->isSync && objcType[0] != _C_ID) {
RCTLogError(
@"Return type of %@.%s should be (id) as the method is \"sync\"",
RCTBridgeModuleNameForClass(_moduleClass),
self.JSMethodName);
}
#endif
_argumentBlocks = argumentBlocks;
}
RCTBridge/RCTCxxBridge
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
[self.batchedBridge enqueueCallback:cbID args:args];
}
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
if (!self.valid) {
return;
}
RCTProfileBeginFlowEvent();
__weak __typeof(self) weakSelf = self;
[self _runAfterLoad:^() {
RCTProfileEndFlowEvent();
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (strongSelf->_reactInstance) {
// instance callJSCallback
strongSelf->_reactInstance->callJSCallback([cbID unsignedLongLongValue], convertIdToFollyDynamic(args ?: @[]));
}
}];
}
Instance
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic&& params) {
SystraceSection s("Instance::callJSCallback");
callback_->incrementPendingJSCalls();
nativeToJsBridge_->invokeCallback((double)callbackId, std::move(params));
}
NativeToJsBridge, 最终又转回到executor中
void NativeToJsBridge::invokeCallback(
double callbackId,
folly::dynamic&& arguments) {
runOnExecutorQueue(
[this, callbackId, arguments = std::move(arguments), systraceCookie](
JSExecutor* executor) {
executor->invokeCallback(callbackId, arguments);
});
}
JSIExecutor
void JSIExecutor::invokeCallback(
const double callbackId,
const folly::dynamic& arguments) {
SystraceSection s("JSIExecutor::invokeCallback", "callbackId", callbackId);
if (!invokeCallbackAndReturnFlushedQueue_) {
bindBridge();
}
Value ret;
try {
ret = invokeCallbackAndReturnFlushedQueue_->call(
*runtime_, callbackId, valueFromDynamic(*runtime_, arguments));
} catch (...) {
std::throw_with_nested(std::runtime_error(
folly::to<std::string>("Error invoking callback ", callbackId)));
}
callNativeModules(ret, true);
}
MessageQueue.js
invokeCallbackAndReturnFlushedQueue(
cbID: number,
args: mixed[],
): null | [Array<number>, Array<number>, Array<mixed>, number] {
this.__guard(() => {
this.__invokeCallback(cbID, args);
});
return this.flushedQueue();
}
// 最终到callbacks映射表中找到对应的callback执行
__invokeCallback(cbID: number, args: mixed[]): void {
this._lastFlush = Date.now();
this._eventLoopStartTime = this._lastFlush;
// The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left.
// eslint-disable-next-line no-bitwise
const callID = cbID >>> 1;
// eslint-disable-next-line no-bitwise
const isSuccess = cbID & 1;
const callback = isSuccess
? this._successCallbacks.get(callID)
: this._failureCallbacks.get(callID);
try {
if (!callback) {
return;
}
this._successCallbacks.delete(callID);
this._failureCallbacks.delete(callID);
callback(...args);
} finally {
if (__DEV__) {
Systrace.endEvent();
}
}
}
MyNativeModule
const {MyNativeModule} = NativeModules;
MyNativeModule.showNativeAlert(
'Hello from React Native!',
(error, result) => {
/// 回到当前callbacks
if (error) {
console.error(error);
} else {
console.log(result);
}
},
);
按照上述流程,一次完整的js方法调用native通信方法调用,已经梳理完毕。希望大家对目前完整的消息流转有个基础的概念。
3. NA -> JS 方法调用
这里再贴出完整代码,由上面JS->NA的代码引导,希望到家可以结合起来看。或者自行查阅代码。
4.总结
本文是源码梳理以及阅读,以消息通信为索引来阅读RN的代码设计,已经初步认识消息是怎么流转起来的。让大家心中有个初步概念。我自己去阅读的时候比较纠结于JS是怎么和C打通的,看了源码以及文章了解 JSI 注入的概念。后面阅读代码就豁然开朗。
后续
后面会以通信为主题做为延伸来介绍几个子主题
- JS解释器
- ModuleRegistry 表
- Bridgeless