美文网首页React native 项目实践iOS DeveloperRN
React Native 源码解析之Initialize Nat

React Native 源码解析之Initialize Nat

作者: Arnold134777 | 来源:发表于2017-04-26 17:53 被阅读697次

    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中的。

    相关文章

      网友评论

        本文标题:React Native 源码解析之Initialize Nat

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