React Native源代码分析--IOS客户端启动流程(从OC到JS)

从RN的架构来看,之所以它在java和js层中间使用一层C++来实现桥接,主要是为了让RN的代码在两个平台中尽量复用。IOS中OC可以和C++任意的相互调用,而在Android中,Java和C++之间也可以通过JNI来进行相互的调用。在早期的RN版本中C++的代码还不是很多,越到后来C++层的代码占比越大,这说明facebook逐步在将所有的可以共用的代码逐渐迁移到C++中,而不需要oc和java写两套。当然由于平台的差异性,很多东西还是得分开。在RN源码的文件夹的组织结构中可以看到,其中React文件夹下就是使用OC编写的IOS端的代码,ReactAndroid是用Java写的Android的,而ReactCommon则是公用的C++代码。
IOS的很多流程其实和Android端的类似,主要流程如下:
React Native各个版本间的代码差异不小,现在网上流行的代码解读,基本上都是0.4X或者以前的,虽然从原理上讲大同小异,但是在实现过程中却又不小的变化。本文就以一个基于0.5X版本的官方示例Awesomeproject来看一下它从启动到最终绘制的整个过程。

启动流程

首先,AppDelegate获取js文件的url地址
其次,创建RCTRootView
最后,将RCTRootView设置为rootViewController的rootView显示
其中最主要的东西都是在创建RootView过程中实现的

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"AwesomeProject"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];

继续跟进下去发现,RCTRootView的创建过程主要是首先创建RTCBridge,然后使用RTCBridge初始化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];
}

这个方法首先创建RCTBridge,然后初拥bridge初始化。
我们首先分析RCTBridge的创建过程:
我们跟踪RTCBridge的调用他的最终实现如下

- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
                       bundleURL:(NSURL *)bundleURL
                  moduleProvider:(RCTBridgeModuleListProvider)block
                   launchOptions:(NSDictionary *)launchOptions
{
  if (self = [super init]) {
    _delegate = delegate;
    _bundleURL = bundleURL;
    _moduleProvider = block;
    _launchOptions = [launchOptions copy];

    [self setUp];
  }
  return self;
}
setUp方法
- (void)setUp
{
  RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBridge setUp]", nil);

  _performanceLogger = [RCTPerformanceLogger new];
  [_performanceLogger markStartForTag:RCTPLBridgeStartup];
  [_performanceLogger markStartForTag:RCTPLTTI];

  Class bridgeClass = self.bridgeClass;

  #if RCT_DEV
  RCTExecuteOnMainQueue(^{
    RCTRegisterReloadCommandListener(self);
  });
  #endif

  // Only update bundleURL from delegate if delegate bundleURL has changed
  NSURL *previousDelegateURL = _delegateBundleURL;
  _delegateBundleURL = [self.delegate sourceURLForBridge:self];
  if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
    _bundleURL = _delegateBundleURL;
  }

  // Sanitize the bundle URL
  _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];

  self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
  [self.batchedBridge start];

  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}

其中我们可以看到bridgeClass的类型是RCTCxxBridge

- (Class)bridgeClass
{
  return [RCTCxxBridge class];
}

因此setUp方法中的 [self.batchedBridge start];实际调用的是[RCTCxxBridge start]方法。start 方法很长,我们来详细分析一下:

- (void)start
{
  RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge start]", nil);
//发送RCTJavaScriptWillStartLoadingNotification的消息
  [[NSNotificationCenter defaultCenter]
    postNotificationName:RCTJavaScriptWillStartLoadingNotification
    object:_parentBridge userInfo:@{@"bridge": self}];

  // Set up the JS thread early,创建JS线程
  _jsThread = [[NSThread alloc] initWithTarget:[self class]
                                      selector:@selector(runRunLoop)
                                        object:nil];
  _jsThread.name = RCTJSThreadName;
  _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
#if RCT_DEBUG
  _jsThread.stackSize *= 2;
#endif
  [_jsThread start];

  dispatch_group_t prepareBridge = dispatch_group_create();

  [_performanceLogger markStartForTag:RCTPLNativeModuleInit];

  [self registerExtraModules];
  // Initialize all native modules that cannot be loaded lazily, 初始化Native module
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  [self registerExtraLazyModules];

  [_performanceLogger markStopForTag:RCTPLNativeModuleInit];

  // This doesn't really do anything.  The real work happens in initializeBridge.
  _reactInstance.reset(new Instance);

  __weak RCTCxxBridge *weakSelf = self;

  // Prepare executor factory (shared_ptr for copy into block)
  std::shared_ptr<JSExecutorFactory> executorFactory;
  if (!self.executorClass) {
    if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
      id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate;
      executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
    }
    if (!executorFactory) {
      executorFactory = std::make_shared<JSIExecutorFactory>(
          makeJSCRuntime(),
          [](const std::string &message, unsigned int logLevel) {
              _RCTLogJavaScriptInternal(
                  static_cast<RCTLogLevel>(logLevel),
                  [NSString stringWithUTF8String:message.c_str()]);
          }, nullptr);
    }
  } else {
    id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];
    executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
      if (error) {
        [weakSelf handleError:error];
      }
    }));
  }

  // Dispatch the instance initialization as soon as the initial module metadata has
  // been collected (see initModules)
  dispatch_group_enter(prepareBridge);
  [self ensureOnJavaScriptThread:^{
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];

  // Load the source asynchronously, then store it for later execution.
  dispatch_group_enter(prepareBridge);
  __block NSData *sourceCode;
  [self loadSource:^(NSError *error, RCTSource *source) {
    if (error) {
      [weakSelf handleError:error];
    }

    sourceCode = source.data;
    dispatch_group_leave(prepareBridge);
  } onProgress:^(RCTLoadingProgress *progressData) {
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
    // Note: RCTDevLoadingView should have been loaded at this point, so no need to allow lazy loading.
    RCTDevLoadingView *loadingView = [weakSelf moduleForName:RCTBridgeModuleNameForClass([RCTDevLoadingView class])
                                       lazilyLoadIfNecessary:NO];
    [loadingView updateProgress:progressData];
#endif
  }];

  // Wait for both the modules and source code to have finished loading
  dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}

代码很长,我们一步步看,他实现的功能主要是:
1.创建JS线程
2.初始化NativeModule
[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
其中,RCTGetModuleClasses() 会返回一个NSArray RCTModuleClasses,而RCTModuleClasses的创建函数是

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

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

  // Register module
  dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
    [RCTModuleClasses addObject:moduleClass];
  });
}

而源码中有对整个函数的宏定义:

define RCT_EXPORT_MODULE(js_name) \

RCT_EXTERN void RCTRegisterModule(Class); \

  • (NSString *)moduleName { return @#js_name; } \
  • (void)load { RCTRegisterModule(self); }
    因此所有通过RCT_EXPORT_MODULE()定义的类(也就是所有对JS层开放的类)都会在初始化的时候加入到RCTModuleClasses的类中。
    最终会调用如下的方法返回一个NSArray<RCTModuleData *> 。其中它会遍历所有的RN module然后创建各自的moduleData。而RCTModuleData中会有Module的所有的对外暴露的方法、属性和一些其他标识位(implementsBatchDidComplete、implementsPartialBatchDidFlush等)。
- (NSArray<RCTModuleData *> *)_registerModulesForClasses:(NSArray<Class> *)moduleClasses
                                        lazilyDiscovered:(BOOL)lazilyDiscovered
{
  RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
                          @"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil);

  NSArray *moduleClassesCopy = [moduleClasses copy];
  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count];
  for (Class moduleClass in moduleClassesCopy) {
    if (RCTTurboModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) {
      continue;
    }
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);

    // Check for module name collisions
    RCTModuleData *moduleData = _moduleDataByName[moduleName];
    if (moduleData) {
      if (moduleData.hasInstance || lazilyDiscovered) {
        // 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 #13258411: can we defer this until config generation?
    moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self];

    _moduleDataByName[moduleName] = moduleData;
    [_moduleClassesByID addObject:moduleClass];
    [moduleDataByID addObject:moduleData];
  }
  [_moduleDataByID addObjectsFromArray:moduleDataByID];

  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");

  return moduleDataByID;
}

3.创建<JSIExecutorFactory> executorFactory
4.在js线程初始化通讯桥
[weakSelf _initializeBridge:executorFactory]->Instance::initializeBridge->nativeToJsBridge:: nativeToJsBridge->jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)->JSIExecutorFactory::createJSExecutor->JSIExecutor::JSIExecutor

JSIExecutor::JSIExecutor(
    std::shared_ptr<jsi::Runtime> runtime,
    std::shared_ptr<ExecutorDelegate> delegate,
    Logger logger,
    const JSIScopedTimeoutInvoker& scopedTimeoutInvoker,
    RuntimeInstaller runtimeInstaller)
    : runtime_(runtime),
      delegate_(delegate),
      nativeModules_(delegate ? delegate->getModuleRegistry() : nullptr),
      logger_(logger),
      scopedTimeoutInvoker_(scopedTimeoutInvoker),
      runtimeInstaller_(runtimeInstaller) {
  runtime_->global().setProperty(
      *runtime, "__jsiExecutorDescription", runtime->description());
}

5.加载js文件

  • (void)loadSource:(RCTSourceLoadBlock)onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
    6.运行js文件
    [strongSelf executeSourceCode:sourceCode sync:NO];->[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion]->[self executeApplicationScript:script url:url async:YES]->self->reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
    sourceUrlStr.UTF8String, !async);->Instance::loadScriptFromString->loadApplication->nativeToJsBridge
    ->loadApplication->JSIExecutor::loadApplicationScript->runtime
    ->evaluateJavaScript

创建完RCTBridge之后,就是初始化:
[self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]-> [self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)];

- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
  RCTAssert(bridge != nil, @"Bridge cannot be nil");
  if (!bridge.valid) {
    return;
  }

  [_contentView removeFromSuperview];
  _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
                                                    bridge:bridge
                                                  reactTag:self.reactTag
                                            sizeFlexiblity:_sizeFlexibility];
  [self runApplication:bridge];

  _contentView.passThroughTouches = _passThroughTouches;
  [self insertSubview:_contentView atIndex:0];

  if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) {
    self.intrinsicContentSize = self.bounds.size;
  }
}

它主要是两个功能,
一是创建RCTRootContentView
二是加载应用
我们先看创建RCTRootContentView

- (instancetype)initWithFrame:(CGRect)frame
                       bridge:(RCTBridge *)bridge
                     reactTag:(NSNumber *)reactTag
               sizeFlexiblity:(RCTRootViewSizeFlexibility)sizeFlexibility
{
  if ((self = [super initWithFrame:frame])) {
    _bridge = bridge;
    self.reactTag = reactTag;
    _sizeFlexibility = sizeFlexibility;
    _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
    [_touchHandler attachToView:self];
    [_bridge.uiManager registerRootView:self];
  }
  return self;
}

在view上加上一个手势识别器,然后在bridge.uiManager中注册自己。
然后,再看加载应用:[self runApplication:bridge];->[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];->strongSelf->reactInstance->callJSFunction([module UTF8String], [method UTF8String],
convertIdToFollyDynamic(args ?: @[]));->ativeToJsBridge
->callFunction(std::move(module), std::move(method),std::move(params));->executor->callFunction->callFunctionReturnFlushedQueue
->call()-> callFunctionReturnFlushedQueue(module: string, method: string, args: any[])
执行完后,执行回调方法callNativeModules(ret, true);- >JSIExecutor::callNativeModules->delegate_->callNativeModules->m_callback->onBatchComplete();最终回调给native模块

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

推荐阅读更多精彩内容