React native 项目实践iOS DeveloperRN

React Native 源码解析之Initialize Nat

2017-04-26  本文已影响697人  Arnold134777

React Native 源码解析之Initialize Native Modules

上一篇的源码分析系列,我们讲解了RN初始化流程:RN代码的加载RCTJavaScriptLoader,本文将继续跟进RN初始化流程中的重要一环:Initialize Native Modules,直接入主题。

上文中我们了解到:[self.batchedBridge start]:初始化js/OC交互所需要的完整的环境配置,入口便从这里开始,找到如下代码段:

// Synchronously initialize all native modules that cannot be loaded lazily
[self initModulesWithDispatchGroup:initModulesAndLoadSource];

下面深入分析其中的代码:

注册代理配置的RNModule

代理中可以自己实现预先加载自己的RNModule,不过目前尚且不太懂什么场景需要实现这个接口,有了解的麻烦回复一下告知一下,谢谢。

NSArray<id<RCTBridgeModule>> *extraModules = nil;
if (self.delegate) {
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
  extraModules = [self.delegate extraModulesForBridge:_parentBridge];     }
} else if (self.moduleProvider) {
extraModules = self.moduleProvider();
}

注册全局其他的RNModule

// The executor is a bridge module, but we want it to be instantiated before
  // any other module has access to the bridge, in case they need the JS thread.
  // TODO: once we have more fine-grained control of init (t11106126) we can
  // probably just replace this with [self moduleForClass:self.executorClass]
  RCT_PROFILE_BEGIN_EVENT(0, @"JavaScriptExecutor", nil);
  if (!_javaScriptExecutor) {
    id<RCTJavaScriptExecutor> executorModule = [self.executorClass new];
    RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:executorModule
                                                                       bridge:self];
    moduleDataByName[moduleData.name] = moduleData;
    [moduleClassesByID addObject:self.executorClass];
    [moduleDataByID addObject:moduleData];
    executorModule.delegate = self;
    // NOTE: _javaScriptExecutor is a weak reference
    _javaScriptExecutor = executorModule;
  }
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");

  // Set up moduleData for automatically-exported modules
  RCT_PROFILE_BEGIN_EVENT(0, @"ModuleData", nil);
  for (Class moduleClass in RCTGetModuleClasses()) {
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);

    // Check for module name collisions
    RCTModuleData *moduleData = moduleDataByName[moduleName];
    if (moduleData) {
      if (moduleData.hasInstance) {
        // 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
        RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
                    "name '%@', but name was already registered by class %@",
                    moduleClass, moduleName, moduleData.moduleClass);
      }
    }

    // Instantiate moduleData (TODO: can we defer this until config generation?)
    moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
                                                     bridge:self];
    moduleDataByName[moduleName] = moduleData;
    [moduleClassesByID addObject:moduleClass];
    [moduleDataByID addObject:moduleData];
  }

为什么需要先初始化一个全局的javaScriptExecutor此处暂时不做详细分析。

外部的RNModule是如何注册进来的呢?实际上RCTModuleClasses全局的数组记录了native注册的NativeModule,以下方法会触发注册:

void RCTRegisterModule(Class moduleClass)
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    RCTModuleClasses = [NSMutableArray new];
  });

  RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
            @"%@ does not conform to the RCTBridgeModule protocol",
            moduleClass);

  // Register module
  [RCTModuleClasses addObject:moduleClass];
}

以下是注册RNModule必须加入的语句,这也解释了NativeModule是如何注册到全局的配置列表的。

/**
 * 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); }

初始化单个的RNModule

上述讲了注册RNModule的列表,下面开始看一下单个RNModule的实体对象类:RCTModuleData的初始化流程,

RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:executorModule
                                                                       bridge:self];
moduleDataByName[moduleData.name] = moduleData;
[moduleClassesByID addObject:self.executorClass];
[moduleDataByID addObject:moduleData];
- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
                                bridge:(RCTBridge *)bridge
{
  if ((self = [super init])) {
    _bridge = bridge;
    _instance = instance;
    _moduleClass = [instance class];
    [self setUp];
  }
  return self;
}

看到上述代码首先让我猜测_bridge = bridge;_instance = instance; 的实现才可以让所有的RNModule都可以获取到全局的bridge,
_instance@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance;RCTModuleData持有的一个实现了RCTBridgeModule协议的实例,即一个RNModule,而我们如何把RCTModuleData持有的bridge传给_instance的,我们看看下面一段代码:

- (void)setBridgeForInstance
{
  if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) {
    RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setBridgeForInstance]", nil);
    @try {
      [(id)_instance setValue:_bridge forKey:@"bridge"];// 访问非public的方法
    }
    @catch (NSException *exception) {
      RCTLogError(@"%@ has no setter or ivar for its bridge, which is not "
                  "permitted. You must either @synthesize the bridge property, "
                  "or provide your own setter method.", self.name);
    }
    RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
  }
}

利用了KVC实现了,因次也解释了为什么自定义RNModule一般需要加入@synthesize bridge = _bridge;

完成ConstantsToExport的配置

我们在构建一个NativeModule可以加入类似如下代码导出native的相关配置给js:

- (NSDictionary<NSString *, id> *)constantsToExport
{
  UIDevice *device = [UIDevice currentDevice];
  return @{
    @"forceTouchAvailable": @(RCTForceTouchAvailable()),
    @"osVersion": [device systemVersion],
    @"interfaceIdiom": interfaceIdiom([device userInterfaceIdiom]),
  };
}

这一段是如何实现的呢?

[self prepareModulesWithDispatchGroup:dispatchGroup];
// Set up modules that require main thread init or constants export
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (whitelistedModules && ![whitelistedModules containsObject:[moduleData moduleClass]]) {
      continue;
    }

    if (moduleData.requiresMainQueueSetup || moduleData.hasConstantsToExport) {
      // Modules that need to be set up on the main thread cannot be initialized
      // lazily when required without doing a dispatch_sync to the main thread,
      // which can result in deadlock. To avoid this, we initialize all of these
      // modules on the main thread in parallel with loading the JS code, so
      // they will already be available before they are ever required.
      dispatch_block_t block = ^{
        if (self.valid) {
          [self->_performanceLogger appendStartForTag:RCTPLNativeModuleMainThread];
          (void)[moduleData instance];
         [moduleData gatherConstants]; // 注册此处核心代码
          [self->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread];
        }
      };

      if (initializeImmediately && RCTIsMainQueue()) {
        block();
      } else {
        // We've already checked that dispatchGroup is non-null, but this satisifies the
        // Xcode analyzer
        if (dispatchGroup) {
          dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
        }
      }
      _modulesInitializedOnMainQueue++;
    }
  }
- (void)gatherConstants
{
  if (_hasConstantsToExport && !_constantsToExport) {
    RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass]), nil);
    (void)[self instance];
    if (!RCTIsMainQueue()) {
      RCTLogWarn(@"Required dispatch_sync to load constants for %@. This may lead to deadlocks", _moduleClass);
    }
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    RCTExecuteOnMainThread(^{
      self->_constantsToExport = [self->_instance constantsToExport] ?: @{};
    }, YES);
#pragma clang diagnostic pop
    RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
  }
}

我们来看看结果:

{
    forceTouchAvailable = 0;
    interfaceIdiom = phone;
    osVersion = "10.0";
}

js端的使用:Platform.ios.js

'use strict';

var Platform = {
  OS: 'ios',
  get Version() {
    return require('NativeModules').IOSConstants.osVersion;
  },
  select: (obj: Object) => obj.ios,
};

module.exports = Platform;

本文先讲到这里,下文继续从这里开始讲解具体js端的页面是如何渲染的,js的方法调用时如何走到native对应的NativeModule中的。

上一篇下一篇

猜你喜欢

热点阅读