react-native源码——启动

rn应用启动时,利用RCTBundleURLProvider获取到js端入口文件index.ios.js的url地址,再生成程序根视图RCTRootView。RCTRootView的创建方法

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(NSDictionary *)initialProperties
                    launchOptions:(NSDictionary *)launchOptions
{
  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
                                            moduleProvider:nil
                                             launchOptions:launchOptions];
  return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}

生成一个bridge,在调用initWithBridge方法,在这个方法中注册一系列通知,用来监听js文件的加载过程,在文件加载完成后刷新视图等。RCTRootView内部有属性强引用bridge对象。

bridge的创建方法主要调用的是如下

- (void)setUp
{
  ... 省略
  _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
  self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
  [self.batchedBridge start];
}

bridge没做啥实事,主要作用就是生成了RCTBatchedBridge对象,这是RCTBridge的一个子类,并强引用了它。
具体干活的对象是RCTBatchedBridge。看它的start方法。

- (void)start
{
  [[NSNotificationCenter defaultCenter]
    postNotificationName:RCTJavaScriptWillStartLoadingNotification
    object:_parentBridge userInfo:@{@"bridge": self}];
  RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge setUp]", nil);
  dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);

  ---------- 标记@1
  dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
  dispatch_group_enter(initModulesAndLoadSource);
  __weak RCTBatchedBridge *weakSelf = self;
  __block NSData *sourceCode;
    ---------- 标记@2
  [self loadSource:^(NSError *error, NSData *source, __unused int64_t sourceLength) {
    if (error) {
      RCTLogWarn(@"Failed to load source: %@", error);
      dispatch_async(dispatch_get_main_queue(), ^{
        [weakSelf stopLoadingWithError:error];
      });
    }
    sourceCode = source;
    dispatch_group_leave(initModulesAndLoadSource);
  } onProgress:^(RCTLoadingProgress *progressData) {
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
    RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
    [loadingView updateProgress:progressData];
#endif
  }];

   ---------- 标记@3
  [self initModulesWithDispatchGroup:initModulesAndLoadSource];

  RCTPerformanceLogger *performanceLogger = self->_performanceLogger;
  __block NSString *config;
  dispatch_group_enter(initModulesAndLoadSource);
  dispatch_async(bridgeQueue, ^{
    
     ---------- 标记@4
    dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();

     ---------- 标记@5
    dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
      [performanceLogger markStartForTag:RCTPLJSCExecutorSetup];
      [weakSelf setUpExecutor];
      [performanceLogger markStopForTag:RCTPLJSCExecutorSetup];
    });

    ---------- 标记@6
    dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
      if (weakSelf.valid) {
        RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge moduleConfig", nil);
        [performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig];
        config = [weakSelf moduleConfig];
        [performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig];
        RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
      }
    });
     ---------- 标记@7
    dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
      [performanceLogger markStartForTag:RCTPLNativeModuleInjectConfig];
      [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
        [performanceLogger markStopForTag:RCTPLNativeModuleInjectConfig];
        if (error) {
          RCTLogWarn(@"Failed to inject config: %@", error);
          dispatch_async(dispatch_get_main_queue(), ^{
            [weakSelf stopLoadingWithError:error];
          });
        }
      }];
      dispatch_group_leave(initModulesAndLoadSource);
    });
  });
   ---------- 标记@8
  dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
    RCTBatchedBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode];
    }
  });
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}

@1: 创建外层的队列组A
@2: 队列组A中的第一个任务,加载我们写的那些js源码文件
@3: 同步的形式,将OC中通过宏RCT_EXPORT_MODULE导出的所有模块信息,例如模块名字、模块对应的RCTModuleData实例等添加到属性_moduleDataByID等之中。同时初始化所有不能懒加载的模块实例。
@4: 创建队列组B,该队列组用于A的一个子任务之中。意思就是队列组A有一个任务,这个任务内部又创建了一个队列组B,B队列组又包含了两个任务,分别是@5和@6。
@5: 队列组B的子任务,初始化js执行器_javaScriptExecutor。
@6: 将@3已经装载的模块信息,按照一定格式组装结构。
@7: @5、@6完成,将模块信息注入js执行上下文中,这样js代码就可以调用了。
@8: @5、@6完成后,未等@7完成,队列组即已经退出,执行@8操作。执行上面加载的js代码,通知RootView页面刷新等。

细究部分:
@3: initModulesWithDispatchGroup方法的实现

- (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
  ... 抽取主要代码
  for (Class moduleClass in RCTGetModuleClasses()) {
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
    RCTModuleData *moduleData = moduleDataByName[moduleName];
    moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
                                                     bridge:self];
    moduleDataByName[moduleName] = moduleData;
    [moduleClassesByID addObject:moduleClass];
    [moduleDataByID addObject:moduleData];
  }
  _moduleDataByID = [moduleDataByID copy];
  _moduleDataByName = [moduleDataByName copy];
  _moduleClassesByID = [moduleClassesByID copy];

  [self prepareModulesWithDispatchGroup:dispatchGroup];
}

方法RCTGetModuleClasses()拿到所有OC中通过RCT_EXPORT_MODULE(js_name)导出的模块名字(若宏后面没写名字,则默认为文件名字),遍历,包装成RCTModuleData类型的数据,存到RCTBatchedBridge的三个属性_moduleDataByID、_moduleDataByName、_moduleClassesByID之中。
@3内部方法prepareModulesWithDispatchGroup实现

- (void)prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
  ... 抽取主要代码
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (moduleData.requiresMainQueueSetup || moduleData.hasConstantsToExport) {
      dispatch_block_t block = ^{
        if (self.valid) {
          [self->_performanceLogger appendStartForTag:RCTPLNativeModuleMainThread];
          (void)[moduleData instance];             --------- 标记p
          [moduleData gatherConstants];            --------- 标记q
          [self->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread];
        }
      };
      dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
}}

意思是找出所有模块中需要预先实例化的模块,初始化它。具体是如果当前模块实现了init或者constantsToExport方法,则上面属性requiresMainQueueSetup和hasConstantsToExport属性会为YES,进入标记p和标记q处,实例化模块。
一般模块采用的是懒加载模式,当js中导入时才加载,但是上面的这两种情况需要在程序启动时就加载,文档意思说是防止死锁,但我没想到应用场景,自己项目中注释掉p和q,也是能正常工作的。这样发现的问题就是一些系统自己使用的模块没有初始化而无法工作,例如RCTDevLoadingView。

2020.7.23新增:0.61版本
1、初始化js线程,启动runloop
2、加载jsbundle源码
3、宏导出的模块在runtime阶段调用的+load方法中将所有模块加载进了一个数组。在这里遍历数组,初始化模块,生成配置表
4、创建js引擎_javaScriptExecutor
5、将配置表注入到js引擎,双端一致
模块内部导出的方法,那个宏定义是多生成了一个特殊前缀的+号方法,在调用模块方法时懒加载模块的所有导出方法(利用runtime查找模块的所有+号方法,抽出特殊前缀的那些方法的集合)。例如导出了一个test方法,实际上会生成-test和+__rct_export__test这样的两个方法,前者是为了在该方法中还可以调用类中的其他方法,后者是专门为了标示,方便runtime时查找的,+号方法而不是-号方法,猜测是一般类中+号方法较少,运行时查找更方便,也可以减少方法同名冲突。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容