ReactNative通信分析- iOS【一】

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 方法调用

  1. 调用NativeModule已经完成注册的对应Module,方法
const {MyNativeModule} = NativeModules;
MyNativeModule.showNativeAlert(
      'Hello from React Native!',
      (error, result) => {
        if (error) {
          console.error(error);
        } else {
          console.log(result);
        }
      },
    );
  1. 寻找到对应的 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;
}
  1. 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;
  1. 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);
    }
  }

  1. 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);
}
  1. 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();
    }
  }
  1. 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);
}
  1. 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 &&params, 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 &params,
    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;
}
  1. 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容