ReactNative通信分析(ModuleRegistry)- iOS【二】

1,918 阅读6分钟

RN:ReactNative 简称,文章中后续使用RN进行代替全称 NA:Native的简称

1. Native ModuleRegistry 是如何收集

先看例子

1.1 RN Module - Networking

/**
 * Bridge module that provides the JS interface to the network stack.
 */
@implementation RCTNetworking 
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(sendRequest
                  : (JS::NativeNetworkingIOS::SpecSendRequestQuery &)query callback
                  : (RCTResponseSenderBlock)responseSender)

{
....
}

RCT_EXPORT_METHOD(abortRequest : (double)requestID)
{
  dispatch_async([self requestQueue], ^{
    [self->_tasksByRequestID[[NSNumber numberWithDouble:requestID]] cancel];
    [self->_tasksByRequestID removeObjectForKey:[NSNumber numberWithDouble:requestID]];
  });
}

RCT_EXPORT_METHOD(clearCookies : (RCTResponseSenderBlock)responseSender)
{
  dispatch_async([self requestQueue], ^{
    NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    if (!storage.cookies.count) {
      responseSender(@[ @NO ]);
      return;
    }
    for (NSHTTPCookie *cookie in storage.cookies) {
      [storage deleteCookie:cookie];
    }
    responseSender(@[ @YES ]);
  });
}

@end

阅读上述代码看见两个关键的宏定义,RCT_EXPORT_MODULE/RCT_EXPORT_METHOD,字面理解到处Networking模块,以及到处对应方法,提供给JS侧使用。那么我们来看看这两段宏定义分别有什么用处。

1.2 RCT_EXPORT_MODULE

/**

 * Place this macro in your class implementation to automatically register

 * your module with the bridge when it loads. The optional js_*name argument*

 * *will be used as the JS module name. If omitted, the JS module name will*

 * *match the Objective-C class name.*

 */

#define RCT_EXPORT_MODULE(js_name)          \

  RCT_EXTERN void RCTRegisterModule(Class); \

  +(NSString *)moduleName                   \
  {                                         \
    return @ #js_name;                      \
  }                                         \
  +(void)load                               \
  {                                         \
    RCTRegisterModule(self);                \
  }
  • 1.1.1 两个static方法,

  • 1.1.2 js_name 标记ModuleName,

  • 1.1.3 load函数启动调用 RCTRegisterModule 方法

每个export_module会在启动阶段主动调用RCTRegisterModule方法,接下来看下RCTRegisterModule实现

1.3 RCTRegisterModule

static NSMutableArray<Class> *RCTModuleClasses;
static dispatch_queue_t RCTModuleClassesSyncQueue;
/**
 * Register the given class as a bridge module. All modules must be registered
 * prior to the first bridge initialization.
 * **TODO: (T115656171) Refactor RCTRegisterModule out of Bridge.m since it doesn't use the Bridge.**
 */

void RCTRegisterModule(Class);
void RCTRegisterModule(Class moduleClass)
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    RCTModuleClasses = [NSMutableArray new];
    RCTModuleClassesSyncQueue =
        dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);

  });
  RCTAssert(
      [moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
      @"%@ does not conform to the RCTBridgeModule protocol",
      moduleClass);
  // Register module
  dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
    [RCTModuleClasses addObject:moduleClass];
  });
}

NSMutableArray *RCTModuleClasses 中添加对应的ModuleClass

RN在启动app完成后,收集了所有的Module的Class存储到数组中。

上述代码中我们可以看出RN在App启动的时候做轻量级别的信息收集,那么在RN-Bridge启动的时候做了什么操作呢,是否完成了 ModuleRegistry 生成以及处理呢?

1.4 Bridge Start

1.4.1 先看Bridge.start

代表RN开始初始化。代码中截取了想要表达部分,_initializeModules: withDispatchGroup: lazilyDiscovered 方法,

    @implementation RCTCxxBridge
    - (void)start {
    /// 截取关键代码
      // Initialize all native modules that cannot be loaded lazily
      (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
    }
    @end

1.4.2 再看Modules的初始化

    - (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<Class> *)modules
                               withDispatchGroup:(dispatch_group_t)dispatchGroup
                                lazilyDiscovered:(BOOL)lazilyDiscovered
{
  // Set up moduleData for automatically-exported modules
  NSArray<RCTModuleData *> *moduleDataById = [self _registerModulesForClasses:modules lazilyDiscovered:lazilyDiscovered];
  if (lazilyDiscovered) {

  } else {
      for (RCTModuleData *moduleData in _moduleDataByID) {
      if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
        (void)[moduleData instance];
      }
    }
  }
  return moduleDataById;
}

// ModuleId 其实是一个递增的int值
namespace {
int32_t getUniqueId()
{
  static std::atomic<int32_t> counter{0};
  return counter++;
}
}
    
- (id<RCTBridgeModule>)instance
{
  NSString *moduleName = [self name];
  int32_t requestId = getUniqueId();
  BridgeNativeModulePerfLogger::moduleCreateStart([moduleName UTF8String], requestId);
  if (!_setupComplete) {
    RCT_PROFILE_BEGIN_EVENT(
        RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData instanceForClass:%@]", _moduleClass]), nil);
    if (_requiresMainQueueSetup) {
      // The chances of deadlock here are low, because module init very rarely
      // calls out to other threads, however we can't control when a module might
      // get accessed by client code during bridge setup, and a very low risk of
      // deadlock is better than a fairly high risk of an assertion being thrown.
      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData instance] main thread setup", nil);
      if (!RCTIsMainQueue()) {
        RCTLogWarn(@"RCTBridge required dispatch_sync to load %@. This may lead to deadlocks", _moduleClass);
      }
      RCTUnsafeExecuteOnMainQueueSync(^{
        [self setUpInstanceAndBridge:requestId];
      });
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
    } else {
      [self setUpInstanceAndBridge:requestId];
    }
    RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
  } else {
    BridgeNativeModulePerfLogger::moduleCreateCacheHit([moduleName UTF8String], requestId);
  }
  if (_instance) {
    BridgeNativeModulePerfLogger::moduleCreateEnd([moduleName UTF8String], requestId);
  } else {
    BridgeNativeModulePerfLogger::moduleCreateFail([moduleName UTF8String], requestId);
  }
  return _instance;
}
  • NSArray<Class> 转换成 NSArray<RCTModuleDatas> ,在上述 _initializeModules 过程中调用 (void)[moduleData instance]; 完成Module在Native的初始化注册。
  • ModuleId 其实就是一个递增int值~
    - (NSArray<RCTModuleData *> *)_registerModulesForClasses:(NSArray<Class> *)moduleClasses
                                        lazilyDiscovered:(BOOL)lazilyDiscovered {
  NSArray *moduleClassesCopy = [moduleClasses copy];
  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count];
  for (Class moduleClass in moduleClassesCopy) {
    if (RCTTurboModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) {
      continue;
    }
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
    // Check for module name collisions
    RCTModuleData *moduleData = _moduleDataByName[moduleName];
    if (moduleData) {
      if (moduleData.hasInstance || lazilyDiscovered) {
        // Existing module was preregistered, so it takes precedence
        continue;
      } else if ([moduleClass new] == nil) {
        // The new module returned nil from init, so use the old module
        continue;
      } else if ([moduleData.moduleClass new] != nil) {
        // Both modules were non-nil, so it's unclear which should take precedence
        RCTLogWarn(
            @"Attempted to register RCTBridgeModule class %@ for the "
             "name '%@', but name was already registered by class %@",
            moduleClass,
            moduleName,
            moduleData.moduleClass);
      }
    }
    // Instantiate moduleData
    // TODO #13258411: can we defer this until config generation?
    int32_t moduleDataId = getUniqueId();
    BridgeNativeModulePerfLogger::moduleDataCreateStart([moduleName UTF8String], moduleDataId);
    moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
                                                     bridge:self
                                             moduleRegistry:_objCModuleRegistry
                                    viewRegistry_DEPRECATED:_viewRegistry_DEPRECATED
                                              bundleManager:_bundleManager
                                          callableJSModules:_callableJSModules];
    BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId);
    
    // Highlight 添加到_moduleDataByID当中
    _moduleDataByName[moduleName] = moduleData;
    [_moduleClassesByID addObject:moduleClass];
    [moduleDataByID addObject:moduleData];
  }
  [_moduleDataByID addObjectsFromArray:moduleDataByID];
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
  return moduleDataByID;
}

上述代码运行完毕后ModuleRegistry在RN启动后RN的初始化代码已经结束,读者们会有好奇并没有介绍 ModuleRegistry有类似的[addmodule forName]的操作,真的注册了么 ?接着往下看。(我第一次看的时候也是有这个疑惑)

1.5 RCTModuleRegistry 实现

1.5.1 先看实现源码

    /*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#import "RCTBridge.h"
#import "RCTTurboModuleRegistry.h"

@class RCTBridgeModule;

@implementation RCTModuleRegistry {
  __weak id<RCTTurboModuleRegistry> _turboModuleRegistry;
  __weak RCTBridge *_bridge;
}

- (void)setBridge:(RCTBridge *)bridge
{
  _bridge = bridge;
}

- (void)setTurboModuleRegistry:(id<RCTTurboModuleRegistry>)turboModuleRegistry
{
  _turboModuleRegistry = turboModuleRegistry;
}

- (id)moduleForName:(const char *)moduleName
{
  return [self moduleForName:moduleName lazilyLoadIfNecessary:YES];
}

- (id)moduleForName:(const char *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad
{
  id<RCTBridgeModule> module = nil;

  RCTBridge *bridge = _bridge;
  if (bridge) {
    module = [bridge moduleForName:[NSString stringWithUTF8String:moduleName] lazilyLoadIfNecessary:lazilyLoad];
  }

  id<RCTTurboModuleRegistry> turboModuleRegistry = _turboModuleRegistry;
  if (module == nil && turboModuleRegistry && (lazilyLoad || [turboModuleRegistry moduleIsInitialized:moduleName])) {
    module = [turboModuleRegistry moduleForName:moduleName];
  }

  return module;
}

- (BOOL)moduleIsInitialized:(Class)moduleClass
{
  RCTBridge *bridge = _bridge;

  if (bridge) {
    return [bridge moduleIsInitialized:moduleClass];
  }

  id<RCTTurboModuleRegistry> turboModuleRegistry = _turboModuleRegistry;
  if (turboModuleRegistry) {
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
    return [turboModuleRegistry moduleIsInitialized:[moduleName UTF8String]];
  }

  return NO;
}

@end

从代码中我们可以看出,RCTModuleRegistry并没有真正的存储Module这些内容,而是从brdige中获取对应的数据和标记。充当了一个中转站,因此我们需要去了解bridge中是如何实现这些的。(思考:为什么不直接在RCTModuleRegistry中做存储和逻辑实现呢?)

1.5.2 RCTCxxBridge中的方法实现

- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad
{
  if (RCTTurboModuleEnabled() && _turboModuleRegistry) {
    const char *moduleNameCStr = [moduleName UTF8String];
    if (lazilyLoad || [_turboModuleRegistry moduleIsInitialized:moduleNameCStr]) {
      id<RCTTurboModule> module = [_turboModuleRegistry moduleForName:moduleNameCStr warnOnLookupFailure:NO];
      if (module != nil) {
        return module;
      }
    }
  }

  if (!lazilyLoad) {
    return _moduleDataByName[moduleName].instance;
  }

  RCTModuleData *moduleData = _moduleDataByName[moduleName];
  if (moduleData) {
    if (![moduleData isKindOfClass:[RCTModuleData class]]) {
      // There is rare race condition where the data stored in the dictionary
      // may have been deallocated, which means the module instance is no longer
      // usable.
      return nil;
    }
    return moduleData.instance;
  }

  // Module may not be loaded yet, so attempt to force load it here.
  // Do this only if the bridge is still valid.
  if (_didInvalidate) {
    return nil;
  }

  const BOOL result = [self.delegate respondsToSelector:@selector(bridge:didNotFindModule:)] &&
      [self.delegate bridge:self didNotFindModule:moduleName];
  if (result) {
    // Try again.
    moduleData = _moduleDataByName[moduleName];
#if RCT_DEV
    // If the `_moduleDataByName` is nil, it must have been cleared by the reload.
  } else if (_moduleDataByName != nil) {
    RCTLogError(@"Unable to find module for %@", moduleName);
  }
#else
  } else {
    RCTLogError(@"Unable to find module for %@", moduleName);
  }
#endif

  return moduleData.instance;
}
  • TurboModule相关逻辑处理
  • Lazy相关判断处理
  • delegate 转发获取兜底Module

总结上述: 本地RCTModuleRegistry维护依赖于_moduleDataByName属性进行维护,其中根据moduleName为key,RCTModuleData为Value的Map。RCTModuleData中有对应Module中所有的属性这些。

好像和我们认知的RCTModuleRegistry映射到JS这段逻辑没有?启动也没有主动做这个事情?答案肯定是有做的。只不过目前这个映射是懒加载触发发生在JS侧。接下来为大家介绍,是如何映射的。

2.来看映射

在上述我们提及到NA是如何声明Module的逻辑,我们先来看下JS侧是如何调对应Module代码的逻辑使用的。

2.1 JS调用

import React, { useState, useEffect } from 'react';
import {View, Text, Button, NativeModules} from 'react-native';

const {MyNativeModule} = NativeModules;

const NativeModuleExample = () => {

  const onPressButton = () => {
    MyNativeModule.showNativeAlert(
      'Hello from React Native!',
      (error, result) => {
        if (error) {
          console.error(error);
        } else {
          console.log(result);
        }
      },
    );
  };

  return <Button title="Show Native Alert" onPress={onPressButton} />;
};

export default NativeModuleExample;

直接看TS代码,我们看不出MyNativeModule是如何生成的,只能感知到,NativeModules中存在该Module,那么我们执行tsc命令,看下js的关键代码

var MyNativeModule = exports.NativeModules.MyNativeModule;

该代码就很清晰,MyNativeModule是由 NativeModules.MyNativeModule 点语法获取到对应的模块。那么移步到NativeModules.d.ts(对NativeModules.js的对外声明),因此我们可以直接看NativeModule.js

    
let NativeModules: {[moduleName: string]: $FlowFixMe, ...} = {};
if (global.nativeModuleProxy) {
  NativeModules = global.nativeModuleProxy;
} else {
  const bridgeConfig = global.__fbBatchedBridgeConfig;
  invariant(
    bridgeConfig,
    '__fbBatchedBridgeConfig is not set, cannot invoke native modules',
  );

  const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty');
  (bridgeConfig.remoteModuleConfig || []).forEach(
    (config: ModuleConfig, moduleID: number) => {
      // Initially this config will only contain the module name when running in JSC. The actual
      // configuration of the module will be lazily loaded.
      const info = genModule(config, moduleID);
      if (!info) {
        return;
      }

      if (info.module) {
        NativeModules[info.name] = info.module;
      }
      // If there's no module config, define a lazy getter
      else {
        defineLazyObjectProperty(NativeModules, info.name, {
          get: () => loadModule(info.name, moduleID),
        });
      }
    },
  );
}

module.exports = NativeModules;

存在两个分支:

  • NativeModules = global.nativeModuleProxy;
  • bridgeConfig.remoteModuleConfig 获取所有配置

目前我的代码中是走到了分支1,nativeModuleProxy。那么我们仔细看下nativeModuleProxy这个东西是个什么东西?

2.2 nativeModuleProxy

void JSIExecutor::initializeRuntime() {
  SystraceSection s("JSIExecutor::initializeRuntime");

  bindNativePerformanceNow(*runtime_);

  runtime_->global().setProperty(
      *runtime_,
      "nativeModuleProxy",
      Object::createFromHostObject(
          *runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));
}

好家伙,原来是个HostObject注入对象呀。NativeModules.MyNativeModule无非就是调用了NativeModuleProxy中的get方法。

2.3 NativeModuleProxy

class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
 public:
  NativeModuleProxy(std::shared_ptr<JSINativeModules> nativeModules)
      : weakNativeModules_(nativeModules) {}

  Value get(Runtime& rt, const PropNameID& name) override {
    if (name.utf8(rt) == "name") {
      return jsi::String::createFromAscii(rt, "NativeModules");
    }

    auto nativeModules = weakNativeModules_.lock();
    if (!nativeModules) {
      return nullptr;
    }

    return nativeModules->getModule(rt, name);
  }

  void set(Runtime&, const PropNameID&, const Value&) override {
    throw std::runtime_error(
        "Unable to put on NativeModules: Operation unsupported");
  }

 private:
  std::weak_ptr<JSINativeModules> weakNativeModules_;
};

清晰可见重载了方法 Value get(Runtime& rt, const PropNameID& name) override,最终调用到nativeModules->getModule(rt, name);返回value。

2.4 JSINativeModules::getModule

Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
  if (!m_moduleRegistry) {
    return nullptr;
  }
  std::string moduleName = name.utf8(rt);
  const auto it = m_objects.find(moduleName);
  if (it != m_objects.end()) {
    return Value(rt, it->second);
  }
  auto module = createModule(rt, moduleName);
  if (!module.has_value()) {
    return nullptr;
  }
  auto result =
      m_objects.emplace(std::move(moduleName), std::move(*module)).first;
  Value ret = Value(rt, result->second);
  return ret;
}

2.5 懒加载对应的Module

std::optional<Object> JSINativeModules::createModule(
  Runtime& rt,
  const std::string& name) {

if (!m_genNativeModuleJS) {
  m_genNativeModuleJS =
      rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
}

auto result = m_moduleRegistry->getConfig(name);
if (!result.has_value()) {
  return std::nullopt;
}

Value moduleInfo = m_genNativeModuleJS->call(
    rt,
    valueFromDynamic(rt, result->config),
    static_cast<double>(result->index));
CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";
CHECK(moduleInfo.isObject())
    << "Module returned from genNativeModule isn't an Object";

std::optional<Object> module(
    moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));
return module;
}
  • m_genNativeModuleJS nativeModule同步info的对象,全局属性中获取
  • moduleInfo 生成获取

2.6 m_genNativeModuleJS

m_genNativeModuleJS = rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");是从JSRuntime中获取的全局属性,那么我们看下这一个全局属性是啥。

// export this method as a global so we can call it from native
global.__fbGenNativeModule = genModule;

function genModule(
  config: ?ModuleConfig,
  moduleID: number,
): ?{
  name: string,
  module?: {...},
  ...
} {
  if (!config) {
    return null;
  }

  const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
  invariant(
    !moduleName.startsWith('RCT') && !moduleName.startsWith('RK'),
    "Module name prefixes should've been stripped by the native side " +
      "but wasn't for " +
      moduleName,
  );

  if (!constants && !methods) {
    // Module contents will be filled in lazily later
    return {name: moduleName};
  }

  const module: {[string]: mixed} = {};
  methods &&
    methods.forEach((methodName, methodID) => {
      const isPromise =
        (promiseMethods && arrayContains(promiseMethods, methodID)) || false;
      const isSync =
        (syncMethods && arrayContains(syncMethods, methodID)) || false;
      invariant(
        !isPromise || !isSync,
        'Cannot have a method that is both async and a sync hook',
      );
      const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
      module[methodName] = genMethod(moduleID, methodID, methodType);
    });

  Object.assign(module, constants);

  if (module.getConstants == null) {
    module.getConstants = () => constants || Object.freeze({});
  } else {
    console.warn(
      `Unable to define method 'getConstants()' on NativeModule '${moduleName}'. NativeModule '${moduleName}' already has a constant or method called 'getConstants'. Please remove it.`,
    );
  }

  if (__DEV__) {
    BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
  }

  return {name: moduleName, module};
}

最终调用genModule完成NativeModule映射到JS侧的功能。

3.总结

  1. NA对本地Class收集
  2. NA启动Bridge,完成ModuleData的收集
  3. JS侧调用懒加载同步ModuleRegistry配置信息