1. 全局的 native module 注册表

RCTModuleClasses 数组

首先, RN中拥有一个全局的静态数组RCTModuleClasses, 使用它来记录所有的Native Module的Class, 可以认为这是一个待启动的Native Module配置表!!!

并且提供了一个全局方法void RCTRegisterModule(Class);, 向这个RCTModuleClasses注入新的module class:

// 1. 全局注册中心, RN 用 RCTModuleClasses 来持有全局注册的 native module
static NSMutableArray<Class> *RCTModuleClasses;
static dispatch_queue_t RCTModuleClassesSyncQueue;
// 2. sync 读 - 全局 RCTModuleClasses
NSArray<Class> *RCTGetModuleClasses(void) {
    __block NSArray<Class> *result;
    dispatch_sync(RCTModuleClassesSyncQueue, ^{
        result = [RCTModuleClasses copy];
    });
    return result;
}
// 3. barrier async 写
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); // sync 读, barrier_async 写
    });
    dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
        [RCTModuleClasses addObject:moduleClass];
    });
}

2. RCTBridgeModule 协议 

APP在pre main将module注入全局表中

RN中如果要实现一个native module, 需要实现RCTBridgeModule协议, 并且RN官方要求我们在实现协议时, 必须加入RCT_EXPORT_MODULE宏.

@protocol RCTBridgeModule <NSObject>
/**
 * 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);                \
  }
...
@end

其中RCT_EXPORT_MODULE(js_name)宏的实现中, 会在 load中调用RCTRegisterModule(self); 方法, 将Native Module Class注入到全局的静态数组RCTModuleClasses中!!!

也就是说, iOS APP在启动执行main函数之前, 所有的实现RCTBridgeModule协议的类型Class, 都会被注入到RCTModuleClasses这个数组中.

3. RCTModuleClasses中Class数据的处理

RCTCxxbridge的start方法执行时, RCTGetModuleClasses()方法里面已经拥有了所有需要注册到bridge的native module配置表, 因此直接可以对这个module配置表中, 所有的 class进行初始化!!!

//(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
- (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<Class> *)modules
                               withDispatchGroup:(dispatch_group_t)dispatchGroup
                                lazilyDiscovered:(BOOL)lazilyDiscovered {
    /// 1. modules Class => 包装成中间管理类 RCTModuleData
    /// 2. 然后将 RCTModuleData 对象放到 bridge 的几个特定容器中管理
    NSArray<RCTModuleData *> *moduleDataById = [self _registerModulesForClasses:modules lazilyDiscovered:lazilyDiscovered];
    if (lazilyDiscovered) {
        ...
    } else { 
        // 初始化时候, 会走这里
        /// 3. 处理 moduleData.hasInstance 的场景, 在 bridge._moduleSetupComplete 之前, module强制进行instance实例化, 并管理在 moduleData 中
        for (RCTModuleData *moduleData in _moduleDataByID) {
            if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
                (void)[moduleData instance];
            }
        }
        /// 4. 标记 bridge 中的 module 完成 setup 工作
        _moduleSetupComplete = YES;
        /// 5. 对 bridge 中的 module 异步 preapre
        [self _prepareModulesWithDispatchGroup:dispatchGroup];
    }
    /// 6. 返回所有的 moduleData 数组
    return moduleDataById;
}

主要来说是做以下几个事情:

  • _registerModulesForClasses, 主要工作: Module Class包装成RCTModuleData:

    将每个moduleClass封装成一个个RCTModuleData 对象moduleData

    然后对moduleData进行setUp()

  • 将RCTModuleData注册到bridge的module管理的属性中:

    NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName

    NSMutableArray<RCTModuleData *> *_moduleDataByID;

    NSMutableArray<Class> *_moduleClassesByID;

  • 部分RCTModuleData模块需要在bridge的_moduleSetupComplete = true之前就进行[moduleData instance] -- (void)[moduleData instance];
  • 标记bridge的_moduleSetupComplete, 标记 bridge的 module setup 完成!!!
  • 异步!!! 大部分的RCTModuleData可以等到bridge完全初始化以后进RCTModuleData instance -- 这类module在实例化时, 会进入调度组dispatchGroup中, 然后异步到mainQueue进行(void)[moduleData instance];

这里有一个重要的标记, RCTCxxBridge.moduleSetupComplete = true, 标记 bridge 的native module setup完成!!!

异步: 大部分的moduleData instance 都会通过dispatchGroup方式, 异步进行[moduleData instance]

4. ModuleClasse包装成RCTModuleData过程

我们知道一个native module被RN bridge使用, 会经历两个过程

  • module class 成员方法的解析
  • module instnace过程

先看第一个, 从Class包装成RCTModuleData时, 发生了什么 1. initWithModuleClass 2. setUp: 帮助判断Class中的一些实例方法和大量配置

- (instancetype)initWithModuleClass:(Class)moduleClass
                     moduleProvider:(RCTBridgeModuleProvider)moduleProvider
                             bridge:(RCTBridge *)bridge
                     moduleRegistry:(RCTModuleRegistry *)moduleRegistry
            viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED
                      bundleManager:(RCTBundleManager *)bundleManager
                  callableJSModules:(RCTCallableJSModules *)callableJSModules
{
  if (self = [super init]) {
    _bridge = bridge;
    _moduleClass = moduleClass;
    _moduleProvider = [moduleProvider copy];
    _moduleRegistry = moduleRegistry;
    _viewRegistry_DEPRECATED = viewRegistry_DEPRECATED;
    _bundleManager = bundleManager;
    _callableJSModules = callableJSModules;
    [self setUp];
  }
  return self;
}
- (void)setUp {
    // 1. instance 是否实现 -batchDidComplete 方法
    _implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)];
    // 2. instance 是否实现 -batchDidComplete 方法
    _implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector(partialBatchDidFlush)];
    // 3. instance 是否是用常量需要暴露出去
    _hasConstantsToExport = [_moduleClass instancesRespondToSelector:@selector(constantsToExport)];
    // 4. module 是否强制在 main queue 中 setup
    const BOOL implementsRequireMainQueueSetup = [_moduleClass respondsToSelector:@selector(requiresMainQueueSetup)];
    if (implementsRequireMainQueueSetup) {
        _requiresMainQueueSetup = [_moduleClass requiresMainQueueSetup];
    } else {
        ...
        // 5. 没实现 requires 时,
        //  - 是否有自定义 -init 初始化方法
        //  - _hasConstantsToExport || hasCustomInit 时, 需要main queue setup
        const BOOL hasCustomInit = !_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod;
        _requiresMainQueueSetup = _hasConstantsToExport || hasCustomInit;
    }
}

其中比较关键的是通过RCTModuleData描述Module Class的几个关键属性:

  • _implementsBatchDidComplete - 标记 instance 是否实现关键方法
  • _implementsPartialBatchDidFlush - 标记 instance 是否实现关键方法
  • _hasConstantsToExport - 标记 module 是否有 Constants Dictionary 暴露给 JS
  • _requiresMainQueueSetup - 注意在_prepareModulesWithDispatchGroup中会使用, 标记 module instance setup 是, 是否强制在main queue中调用. RN默认会在子线程中进行module instance setup

5. RCTModuleData在什么时候进行module instance

native module在真正被JS使用之前, 需要对RCTModuleData进行模块实例化 -- module instnace, 也称为module instance setup, 前面提到过, 大量的RCTModuleData的module instnace过程会在RCTCxxBridge._prepareModulesWithDispatchGroup(...)方法中实现:

/// RCTCxxBridge 处理所有的 RCTModuleData, 对它们调用 `moduleData instance` 方法的过程
- (void)_prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup {
    // 1. 根据是否有 dispatchGroup 决定后续进行 `module instance` 是否异步进行
    BOOL initializeImmediately = NO;
    if (dispatchGroup == NULL) {
        RCTAssertMainQueue();
        initializeImmediately = YES;
    }
    // 2. _moduleDataByID 中缓存着所有的`module class`构造的`RCTModuleData`, 遍历它们, 构造一个 block, 根据 initializeImmediately 参数, 决定是否立即执行
    for (RCTModuleData *moduleData in _moduleDataByID) {
        // 3. 注意, 强制 moduleData.requiresMainQueueSetup, 也就是`module instance`强制在main queue 进行`setup`
        if (moduleData.requiresMainQueueSetup) {
            // 4. `module instance setup` 的过程, 直接主动调用 [moduleData instance], 并强制触发 `[moduleData gatherConstants]`, 收集 instance 要暴露给 JS 的常量Dictionary
            dispatch_block_t block = ^{
                if (self.valid && ![moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) {
                    (void)[moduleData instance];
                    if (!RCTIsMainQueueExecutionOfConstantsToExportDisabled()) {
                        [moduleData gatherConstants];
                    }
                }
            };
            if (initializeImmediately && RCTIsMainQueue()) {
                block();
            } else {
                if (dispatchGroup) {
                    dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
                }
            }
            // 5. RCTCxxBridge 中 记录所有在  main queue 中 进行 `module instance` 的module个数
            _modulesInitializedOnMainQueue  ;
        }
    }
}

通过上面的代码, 我们能看到: 只有moduleData.requiresMainQueueSetup == true时, 才会在启动阶段进行module instance, 如果没有强制要求在主线程进行module instance setup那么就是 lazy module instance, 或者称为懒实例化的模块!!!

通过前面的RN的代码, 我们能看到, 有以下几种场景, module instance被强制在启动阶段被module instance:

  • 在实现RCTBridgeModule协议时, requiresMainQueueSetup 方法返回 true
  • 如果没有实现 requiresMainQueueSetup方法, 判断 hasCustomInit || _hasConstantsToExport, 也就是如果有 自定义-init方法, 或者 Constants暴露给JS, 必须强制在 main queue setup

5. RCTModuleData在进行module instance的细节

直接贴上 RCTModuleData的instance方法, 其中最重要的是方法-setUpInstanceAndBridge:

- (id<RCTBridgeModule>)instance {
    ...
    // 1. _setupComplete 标记 module instance 是否 setup 完成
    if(!_setupComplete) {
        [self setUpInstanceAndBridge:requestId];
    }
    ...
    return _instance;
}
- (void)setUpInstanceAndBridge:(int32_t)requestId {
    NSString *moduleName = [self name];
    // 注意: 使用 instanceLock 保护 _instance 成员
    {
        std::unique_lock<std::mutex> lock(_instanceLock);
        // 1. 判断 moduleData._setupComplete 是否完成 setup
        BOOL shouldSetup = !_setupComplete && _bridge.valid;
        // 2. 如果 moduleData._instance 没实例化, 使用 _moduleProvider() 实例化
        // - 如果实例化失败, 也需要标记 _setupComplete = true
        if (shouldSetup) {
            if (!_instance) {
                // 使用 moduleProvider() 调用实例化
                _instance = _moduleProvider ? _moduleProvider() : nil;
                if (!_instance) {
                    _setupComplete = YES;
                }
            }
        }
        // 3. 将常见属性(bridge, moduleRegistry...)注入到 module instnace 中!
        if (shouldSetup) {
            [self setBridgeForInstance];
            [self setModuleRegistryForInstance];
            [self setViewRegistryForInstance];
            [self setBundleManagerForInstance];
            [self setCallableJSModulesForInstance];
        }
        // 4. 初始化 module instance 的 methodQueue
        [self setUpMethodQueue];
        // 5. 调用 module instance 中的 initialize 方法, 如果实现了的话
        if (shouldSetup) {
            [self _initializeModule];
        }
    } // instanceLock 释放
    // 6. 什么时候通知全局 module instance 完成!!!
    if (_bridge.moduleSetupComplete) {
        // 6.1 大部分 module instance 是在 moduleSetupComplete = ture 执行, 会主动通知全局
        [self finishSetupForInstance];
    } else {  
        // 6.2 少部分在 moduleSetupComplete = false 执行, 标记 _requiresMainQueueSetup = NO, 这样 module instance 实际只setup 一半, lazy instance 使用时, `_bridge.moduleSetupComplete` 一定为 true, 再进行通知, 调用 `finishSetupForInstance`方法
        _requiresMainQueueSetup = NO;
    }
}
- (void)setUpMethodQueue {
    if (_instance && !_methodQueue && _bridge.valid) {
        // 1. instance 是否主动指定 methodQueue, moduleData持有
        BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
        if (implementsMethodQueue && _bridge.valid) {
            _methodQueue = _instance.methodQueue;
        }
        // 2. instance 没有指定, 创建 子线程, 作为 methodQueue
        if (!_methodQueue && _bridge.valid) {
            _queueName = [NSString stringWithFormat:@"com.facebook.react.%@Queue", self.name];
            _methodQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
            // 3. 如果`module instance` 主动实现 methodQueue, 使用KVC设置给`module instance` methodQueue!!!
            if (implementsMethodQueue) {
                @try {
                    [(id)_instance setValue:_methodQueue forKey:@"methodQueue"];
                } @catch (NSException *exception) {
                    RCTLogError();
                }
            }
      }
}
// 如果 `module instance` 实现 `RCTBridgeModule`协议的`-initialize`方法, 主动调用, 并标记`_isInitialized`执行过
- (void)_initializeModule {
    if (!_isInitialized && [_instance respondsToSelector:@selector(initialize)]) {
        _isInitialized = YES;
        [(id<RCTInitializing>)_instance initialize];
    }
}
// 如果执行这个方法, 通知bridge   全局: 这个 module `setupComplete`!!!
- (void)finishSetupForInstance {
    if (!_setupComplete && _instance) {
        _setupComplete = YES;
        [_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
        [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification object:_bridge userInfo:@{@"module" : _instance, @"bridge" : RCTNullIfNil(_bridge.parentBridge)}];
    }
}

在module instance过程中, 有以下几个点需要注意:

  • instance = _moduleProvider(), 而 _moduleProvider实现基本都是[moduleClass new], 也就是说使用-init进行初始化.
  • 能看给module instance注入大量属性(bridge, ModuelRegistry...)时, 都是使用的 KVC, 并包装在try-catch中, 因为RCTBridgeModule协议实现了接口, 如果 module需要使用这些注入的 API, 需要手动使用@synthesize生成对应的成员变量.
  • 关于method queue, 后续 JS 调用 module instance的export method时, 会异步到method queue中执行.
  • 调用module instance的-initialize是RCTBridgeModule协议提供的, 请与OC中的 initiali...进行区分

关于finishSetupForInstance的调用时机, 请参考代码注释. 另外, 只要它调用会进行如下操作:

  • 标记 _setupComplete = YES
  • 需要通知并更新, bridge中FrameUpdates相关的module
  • 全局通知RCTDidInitializeModuleNotification

以上就是iOS RN启动中管理Native Module详解的详细内容,更多关于iOS RN启动管理Native Module的资料请关注Devmax其它相关文章!

iOS RN启动中管理Native Module详解的更多相关文章

  1. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  2. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  4. ios – Testflight无法安装应用程序

    我有几个测试人员注册了testflight并连接了他们的设备……他们有不同的ios型号……但是所有这些都有同样的问题.当他们从“safari”或“testflight”应用程序本身单击应用程序的安装按钮时……达到约90%并出现错误消息…

  5. ibm-mobilefirst – 在iOS 7.1上获取“无法安装应用程序,因为证书无效”错误

    当我的客户端将他们的设备更新到iOS7.1,然后尝试从AppCenter更新我们的应用程序时,我收到了上述错误.经过一番搜索,我找到了一个类似问题的帖子here.但是后来因为我在客户端使用AppCenter更新应用程序的环境中,我无法使用USB插件并为他们安装应用程序.在发布支持之前,是否有通过AppCenter进行下载的解决方法?

  6. ios – 视图的简单拖放?

    我正在学习iOS,但我找不到如何向UIView添加拖放行为.我试过了:它说“UIView没有可见的接口声明选择器addTarget”此外,我尝试添加平移手势识别器,但不确定这是否是我需要的它被称为,但不知道如何获得事件的坐标.在iOS中注册移动事件回调/拖放操作的标准简单方法是什么?

  7. ios – 什么控制iTunes中iPhone应用程序支持的语言列表?

    什么控制iPhone应用程序的iTunes页面中支持的语言?

  8. ios – 获得APNs响应BadDeviceToken或Unregistered的可能原因是什么?

    我知道设备令牌在某些时候是有效的.用户如何使其设备令牌变坏?从关于“未注册”的文档:Thedevicetokenisinactiveforthespecifiedtopic.这是否意味着应用程序已被删除?.您应该看到四种分发方法:如果您选择AppStore或Enterprise,您将在后面的对话框中看到Xcode将APNS权利更改为生产:如果选择AdHoc或Development,则aps-environment下的文本将是开发,然后应与后端的配置匹配.

  9. ios – 当我关闭应用程序时,我从调试器获得消息:由于信号15而终止

    我怎么能解决这个问题,我不知道这个链接MypreviousproblemaboutCoredata对我的问题有影响吗?当我cmd应用程序的Q时,将出现此消息.Messagefromdebugger:Terminatedduetosignal15如果谁知道我以前的问题的解决方案,请告诉我.解决方法>来自调试器的消息:每当用户通过CMD-Q(退出)或STOP手动终止应用程序(无论是在iOS模拟器中还是

  10. ios – NSUbiquityIdentityDidChangeNotification和SIGKILL

    当应用程序被发送到后台时,我们会删除观察者吗?我遇到的问题是,当UbiquityToken发生变化时,应用程序终止,因为用户已经更改了iCloud设置.你们如何设法订阅这个通知,如果你不这样做,你会做什么来跟踪当前登录的iCloud用户?

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部