小李喇叭 發表於 2022-9-7 09:50:35

iOS RN启动中管理Native Module详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>1. 全局的 native module 注册表</li><li>2. RCTBridgeModule 协议&nbsp;</li><li>3. RCTModuleClasses中Class数据的处理</li><li>4. ModuleClasse包装成RCTModuleData过程</li><li>5. RCTModuleData在什么时候进行module instance</li><li>5. RCTModuleData在进行module instance的细节</li></ul></div><p class="maodian"></p><h2>1. 全局的 native module 注册表</h2>
<p>RCTModuleClasses 数组</p>
<p>首先, RN中拥有一个全局的静态数组RCTModuleClasses, 使用它来记录所有的Native Module的Class, 可以认为这是一个待启动的Native Module配置表!!!</p>
<p>并且提供了一个全局方法void RCTRegisterModule(Class);, 向这个RCTModuleClasses注入新的module class:</p>
<div class="jb51code"><pre class="brush:cpp;">// 1. 全局注册中心, RN 用 RCTModuleClasses 来持有全局注册的 native module
static NSMutableArray&lt;Class&gt; *RCTModuleClasses;
static dispatch_queue_t RCTModuleClassesSyncQueue;
// 2. sync 读 - 全局 RCTModuleClasses
NSArray&lt;Class&gt; *RCTGetModuleClasses(void) {
    __block NSArray&lt;Class&gt; *result;
    dispatch_sync(RCTModuleClassesSyncQueue, ^{
      result = ;
    });
    return result;
}
// 3. barrier async 写
void RCTRegisterModule(Class moduleClass) {
    static dispatch_once_t onceToken;
    dispatch_once(&amp;onceToken, ^{
      RCTModuleClasses = ;
      RCTModuleClassesSyncQueue = dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT); // sync 读, barrier_async 写
    });
    dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
      ;
    });
}
</pre></div>
<p class="maodian"></p><h2>2. RCTBridgeModule 协议&nbsp;</h2>
<p>APP在pre main将module注入全局表中</p>
<p>RN中如果要实现一个native module, 需要实现RCTBridgeModule协议, 并且RN官方要求我们在实现协议时, 必须加入RCT_EXPORT_MODULE宏.</p>
<div class="jb51code"><pre class="brush:cpp;">@protocol RCTBridgeModule &lt;NSObject&gt;
/**
* 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
</pre></div>
<p>其中RCT_EXPORT_MODULE(js_name)宏的实现中, 会在+load中调用RCTRegisterModule(self); 方法, 将Native Module Class注入到全局的静态数组RCTModuleClasses中!!!</p>
<p>也就是说, iOS APP在启动执行main函数之前, 所有的实现RCTBridgeModule协议的类型Class, 都会被注入到RCTModuleClasses这个数组中.</p>
<p class="maodian"></p><h2>3. RCTModuleClasses中Class数据的处理</h2>
<p>RCTCxxbridge的start方法执行时, RCTGetModuleClasses()方法里面已经拥有了所有需要注册到bridge的native module配置表, 因此直接可以对这个module配置表中, 所有的 class进行初始化!!!</p>
<div class="jb51code"><pre class="brush:cpp;">//(void);
- (NSArray&lt;RCTModuleData *&gt; *)_initializeModules:(NSArray&lt;Class&gt; *)modules
                               withDispatchGroup:(dispatch_group_t)dispatchGroup
                              lazilyDiscovered:(BOOL)lazilyDiscovered {
    /// 1. modules Class =&gt; 包装成中间管理类 RCTModuleData
    /// 2. 然后将 RCTModuleData 对象放到 bridge 的几个特定容器中管理
    NSArray&lt;RCTModuleData *&gt; *moduleDataById = ;
    if (lazilyDiscovered) {
      ...
    } else {
      // 初始化时候, 会走这里
      /// 3. 处理 moduleData.hasInstance 的场景, 在 bridge._moduleSetupComplete 之前, module强制进行instance实例化, 并管理在 moduleData 中
      for (RCTModuleData *moduleData in _moduleDataByID) {
            if (moduleData.hasInstance &amp;&amp; (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
                (void);
            }
      }
      /// 4. 标记 bridge 中的 module 完成 setup 工作
      _moduleSetupComplete = YES;
      /// 5. 对 bridge 中的 module 异步 preapre
      ;
    }
    /// 6. 返回所有的 moduleData 数组
    return moduleDataById;
}
</pre></div>
<p>主要来说是做以下几个事情:</p>
<ul><li>_registerModulesForClasses, 主要工作: Module Class包装成RCTModuleData:<p>将每个moduleClass封装成一个个RCTModuleData 对象moduleData</p>
<p>然后对moduleData进行setUp()</p></li><li>将RCTModuleData注册到bridge的module管理的属性中:<p>NSMutableDictionary&lt;NSString *, RCTModuleData *&gt; *_moduleDataByName</p>
<p>NSMutableArray&lt;RCTModuleData *&gt; *_moduleDataByID;</p>
<p>NSMutableArray&lt;Class&gt; *_moduleClassesByID;</p></li><li>部分RCTModuleData模块需要在bridge的_moduleSetupComplete = true之前就进行 -- (void);</li><li>标记bridge的_moduleSetupComplete, 标记 bridge的 module setup 完成!!!</li><li>异步!!! 大部分的RCTModuleData可以等到bridge完全初始化以后进RCTModuleData instance -- 这类module在实例化时, 会进入调度组dispatchGroup中, 然后异步到mainQueue进行(void);</li></ul>
<p>这里有一个重要的标记, RCTCxxBridge.moduleSetupComplete = true, 标记 bridge 的native module setup完成!!!</p>
<p>异步: 大部分的moduleData instance 都会通过dispatchGroup方式, 异步进行</p>
<p class="maodian"></p><h2>4. ModuleClasse包装成RCTModuleData过程</h2>
<p>我们知道一个native module被RN bridge使用, 会经历两个过程</p>
<ul><li>module class 成员方法的解析</li><li>module instnace过程</li></ul>
<p>先看第一个, 从Class包装成RCTModuleData时, 发生了什么 1. initWithModuleClass 2. setUp: 帮助判断Class中的一些实例方法和大量配置</p>
<div class="jb51code"><pre class="brush:cpp;">- (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 = ) {
    _bridge = bridge;
    _moduleClass = moduleClass;
    _moduleProvider = ;
    _moduleRegistry = moduleRegistry;
    _viewRegistry_DEPRECATED = viewRegistry_DEPRECATED;
    _bundleManager = bundleManager;
    _callableJSModules = callableJSModules;
    ;
}
return self;
}
- (void)setUp {
    // 1. instance 是否实现 -batchDidComplete 方法
    _implementsBatchDidComplete = ;
    // 2. instance 是否实现 -batchDidComplete 方法
    _implementsPartialBatchDidFlush = ;
    // 3. instance 是否是用常量需要暴露出去
    _hasConstantsToExport = ;
    // 4. module 是否强制在 main queue 中 setup
    const BOOL implementsRequireMainQueueSetup = ;
    if (implementsRequireMainQueueSetup) {
      _requiresMainQueueSetup = ;
    } else {
      ...
      // 5. 没实现 requires 时,
      //- 是否有自定义 -init 初始化方法
      //- _hasConstantsToExport || hasCustomInit 时, 需要main queue setup
      const BOOL hasCustomInit = !_instance &amp;&amp; != objectInitMethod;
      _requiresMainQueueSetup = _hasConstantsToExport || hasCustomInit;
    }
}
</pre></div>
<p>其中比较关键的是通过RCTModuleData描述Module Class的几个关键属性:</p>
<ul><li>_implementsBatchDidComplete - 标记 instance 是否实现关键方法</li><li>_implementsPartialBatchDidFlush - 标记 instance 是否实现关键方法</li><li>_hasConstantsToExport - 标记 module 是否有 Constants Dictionary 暴露给 JS</li><li>_requiresMainQueueSetup - 注意在_prepareModulesWithDispatchGroup中会使用, 标记 module instance setup 是, 是否强制在main queue中调用. RN默认会在子线程中进行module instance setup</li></ul>
<p class="maodian"></p><h2>5. RCTModuleData在什么时候进行module instance</h2>
<p>native module在真正被JS使用之前, 需要对RCTModuleData进行模块实例化 -- module instnace, 也称为module instance setup, 前面提到过, 大量的RCTModuleData的module instnace过程会在RCTCxxBridge._prepareModulesWithDispatchGroup(...)方法中实现:</p>
<div class="jb51code"><pre class="brush:cpp;">/// 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` 的过程, 直接主动调用 , 并强制触发 ``, 收集 instance 要暴露给 JS 的常量Dictionary
            dispatch_block_t block = ^{
                if (self.valid &amp;&amp; !]) {
                  (void);
                  if (!RCTIsMainQueueExecutionOfConstantsToExportDisabled()) {
                        ;
                  }
                }
            };
            if (initializeImmediately &amp;&amp; RCTIsMainQueue()) {
                block();
            } else {
                if (dispatchGroup) {
                  dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
                }
            }
            // 5. RCTCxxBridge 中 记录所有在main queue 中 进行 `module instance` 的module个数
            _modulesInitializedOnMainQueue++;
      }
    }
}
</pre></div>
<p>通过上面的代码, 我们能看到: 只有moduleData.requiresMainQueueSetup == true时, 才会在启动阶段进行module instance, 如果没有强制要求在主线程进行module instance setup那么就是 lazy module instance, 或者称为懒实例化的模块!!!</p>
<p>通过前面的RN的代码, 我们能看到, 有以下几种场景, module instance被强制在启动阶段被module instance:</p>
<ul><li>在实现RCTBridgeModule协议时, +requiresMainQueueSetup 方法返回 true</li><li>如果没有实现+requiresMainQueueSetup方法, 判断 hasCustomInit || _hasConstantsToExport, 也就是如果有 自定义-init方法, 或者 Constants暴露给JS, 必须强制在 main queue setup</li></ul>
<p class="maodian"></p><h2>5. RCTModuleData在进行module instance的细节</h2>
<p>直接贴上 RCTModuleData的instance方法, 其中最重要的是方法-setUpInstanceAndBridge:</p>
<div class="jb51code"><pre class="brush:cpp;">- (id&lt;RCTBridgeModule&gt;)instance {
    ...
    // 1. _setupComplete 标记 module instance 是否 setup 完成
    if(!_setupComplete) {
      ;
    }
    ...
    return _instance;
}
- (void)setUpInstanceAndBridge:(int32_t)requestId {
    NSString *moduleName = ;
    // 注意: 使用 instanceLock 保护 _instance 成员
    {
      std::unique_lock&lt;std::mutex&gt; lock(_instanceLock);
      // 1. 判断 moduleData._setupComplete 是否完成 setup
      BOOL shouldSetup = !_setupComplete &amp;&amp; _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) {
            ;
            ;
            ;
            ;
            ;
      }
      // 4. 初始化 module instance 的 methodQueue
      ;
      // 5. 调用 module instance 中的 initialize 方法, 如果实现了的话
      if (shouldSetup) {
            ;
      }
    } // instanceLock 释放
    // 6. 什么时候通知全局 module instance 完成!!!
    if (_bridge.moduleSetupComplete) {
      // 6.1 大部分 module instance 是在 moduleSetupComplete = ture 执行, 会主动通知全局
      ;
    } else {
      // 6.2 少部分在 moduleSetupComplete = false 执行, 标记 _requiresMainQueueSetup = NO, 这样 module instance 实际只setup 一半, lazy instance 使用时, `_bridge.moduleSetupComplete` 一定为 true, 再进行通知, 调用 `finishSetupForInstance`方法
      _requiresMainQueueSetup = NO;
    }
}
- (void)setUpMethodQueue {
    if (_instance &amp;&amp; !_methodQueue &amp;&amp; _bridge.valid) {
      // 1. instance 是否主动指定 methodQueue, moduleData持有
      BOOL implementsMethodQueue = ;
      if (implementsMethodQueue &amp;&amp; _bridge.valid) {
            _methodQueue = _instance.methodQueue;
      }
      // 2. instance 没有指定, 创建 子线程, 作为 methodQueue
      if (!_methodQueue &amp;&amp; _bridge.valid) {
            _queueName = ;
            _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 &amp;&amp; ) {
      _isInitialized = YES;
      [(id&lt;RCTInitializing&gt;)_instance initialize];
    }
}
// 如果执行这个方法, 通知bridge + 全局: 这个 module `setupComplete`!!!
- (void)finishSetupForInstance {
    if (!_setupComplete &amp;&amp; _instance) {
      _setupComplete = YES;
      ;
      [ postNotificationName:RCTDidInitializeModuleNotification object:_bridge userInfo:@{@"module" : _instance, @"bridge" : RCTNullIfNil(_bridge.parentBridge)}];
    }
}
</pre></div>
<p>在module instance过程中, 有以下几个点需要注意:</p>
<ul><li>instance = _moduleProvider(), 而 _moduleProvider实现基本都是, 也就是说使用-init进行初始化.</li><li>能看给module instance注入大量属性(bridge, ModuelRegistry...)时, 都是使用的 KVC, 并包装在try-catch中, 因为RCTBridgeModule协议实现了接口, 如果 module需要使用这些注入的 API, 需要手动使用@synthesize生成对应的成员变量.</li><li>关于method queue, 后续 JS 调用 module instance的export method时, 会异步到method queue中执行.</li><li>调用module instance的-initialize是RCTBridgeModule协议提供的, 请与OC中的+initiali...进行区分</li></ul>
<p>关于finishSetupForInstance的调用时机, 请参考代码注释. 另外, 只要它调用会进行如下操作:</p>
<ul><li>标记 _setupComplete = YES</li><li>需要通知并更新, bridge中FrameUpdates相关的module</li><li>全局通知RCTDidInitializeModuleNotification</li></ul>
<p>以上就是iOS RN启动中管理Native Module详解的详细内容,更多关于iOS RN启动管理Native Module的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>iOS开发中关键字const/static/extern、UIKIT_EXTERN的区别和用法</li><li>IOS 关键字const 、static、extern详解</li><li>iOS10通知框架UserNotification理解与应用</li><li>iOS实现启动引导页与指纹解锁的方法详解</li><li>iOS启动页倒计时跳过按钮功能</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: iOS RN启动中管理Native Module详解