美文网首页
ReactNative通信分析- iOS【一】

ReactNative通信分析- iOS【一】

作者: 呵呵_7380 | 来源:发表于2024-05-13 09:41 被阅读0次

    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

    相关文章

      网友评论

          本文标题:ReactNative通信分析- iOS【一】

          本文链接:https://www.haomeiwen.com/subject/eizjfjtx.html