非常詳細(xì)??!React-Native iOS源碼閱讀紀(jì)錄

本文是在看過一位大神的總結(jié)之后,又親自根據(jù)文章看了一遍源碼,算是對大神的文章做一些補充。下面給出文章鏈接。閱讀本文最好參考文章的函數(shù)調(diào)用順序以及源碼會更加明朗。
http://www.cocoachina.com/programmer/20170505/19189.html

一.RN初始化過程

React-Native本身是通過iOS提供的JavaScriptCore框架進(jìn)行通信的。
下面我們來看一下加載流程。首先找到RN的入口,也就是創(chuàng)建RCTRootView的地方:

rootView = RCTRootView.init(bundleURL: jsPath, moduleName:"BindPhone", initialProperties: dict, launchOptions: nil)

這個方法內(nèi)部會調(diào)到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];
}

在這個方法內(nèi)部會創(chuàng)建一個RCTBridge對象并且會調(diào)用另外一個構(gòu)造函數(shù),我們先來看RCTBridge初始化的時候做了什么?順著方法調(diào)用鏈,我們來到了RCTBridge的setUp方法

- (void)setUp
{
  RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBridge setUp]", nil);

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

  // 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 createBatchedBridge];
  [self.batchedBridge start];

  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}

在這個函數(shù)里我們發(fā)現(xiàn)RN會創(chuàng)建一個RCTBatchedBridge,并且把RCTBatchedBridge作為自己的一個屬性。在RCTBatchedBridge內(nèi)部會把當(dāng)前的RCTBridge對象作為一個全局靜態(tài)屬性。
接下來就會執(zhí)行RCTBatchedBridge的start方法。由于這個start方法比較長,我們只截取一部分來說。

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

  dispatch_group_t initModulesAndLoadSource = dispatch_group_create();

  // Asynchronously load source code
  dispatch_group_enter(initModulesAndLoadSource);
  __weak RCTBatchedBridge *weakSelf = self;
  __block NSData *sourceCode;
  [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) {
#ifdef RCT_DEV
    RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
    [loadingView updateProgress:progressData];
#endif
  }];

  // Synchronously initialize all native modules that cannot be loaded lazily
  [self initModulesWithDispatchGroup:initModulesAndLoadSource];
}

首先會觸發(fā)一個RCTJavaScriptWillStartLoadingNotification通知,從通知命名一看便知這里是告訴我們即將要加載JavaScript代碼。然后就會去調(diào)用loadSource方法,要注意這里loadSource有同步加載和異步加載兩種情況,如果同步加載失敗就會去調(diào)用異步加載的方法,最后加載得到的是一個NSData對象。
接下來會調(diào)用initModulesWithDispatchGroup方法并且把一個GCD group作為參數(shù)傳進(jìn)去,通過名字便可知這個方法是做一些OC原生Modules的初始化工作,也就是說RN在這個方法里面會拿到OC要暴露給RN的類和方法。我們看一下initModulesWithDispatchGroup方法:

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

RCTGetModuleClasses()會把遵守RCTBridgeModule的類都裝到數(shù)組里并返回,然后根據(jù)moduleName創(chuàng)建ModuleData并緩存到全局的字典中。還會把moduleClass放到moduleClassesByID,moduleData放到moduleDataByID兩個實例數(shù)組中。到這里所有要暴露給JS的類和方法都被裝到了RCTBatchedBridge的實例容器中。這里我要補充下,ModuleData里面有一個requiresMainQueueSetup表明當(dāng)前模塊是否在主線程執(zhí)行,這樣就避免了UI事件在子線程中執(zhí)行。
moduleData準(zhǔn)備完畢之后,還會做一些初始化js執(zhí)行器等等操作,并且把配置表寫入JS的操作,這里用到了dispatch_group來控制當(dāng)javascriptExcutor初始化完畢,配置表加載完畢之后再執(zhí)行JS代碼

//接上文initModulesWithDispatchGroup方法
__block NSString *config;
  dispatch_group_enter(initModulesAndLoadSource);
  dispatch_async(bridgeQueue, ^{
    dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();

    // Asynchronously initialize the JS executor
    dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
      [performanceLogger markStartForTag:RCTPLJSCExecutorSetup];
      [weakSelf setUpExecutor];
      [performanceLogger markStopForTag:RCTPLJSCExecutorSetup];
    });

    // Asynchronously gather the module config
    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, @"");
      }
    });

    dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
      // We're not waiting for this to complete to leave dispatch group, since
      // injectJSONConfiguration and executeSourceCode will schedule operations
      // on the same queue anyway.
      [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);
    });
  });

  dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
    RCTBatchedBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode];
    }
  });

在這里注意三個方法setUpExecutor,moduleConfig和injectJSONConfiguration。
先來說說setUpExecutor,在這里會調(diào)到RCTJSCExecutor setUp方法。在setUp主要看調(diào)用了executeBlockOnJavaScriptQueue為JS添加了一些全局變量

[self executeBlockOnJavaScriptQueue:^{
    if (!self.valid) {
      return;
    }

    JSContext *context = nil;
    if (self->_jscWrapper) {
      RCTAssert(self->_context != nil, @"If wrapper was pre-initialized, context should be too");
      context = self->_context.context;
    } else {
      [self->_performanceLogger markStartForTag:RCTPLJSCWrapperOpenLibrary];
      self->_jscWrapper = RCTJSCWrapperCreate(self->_useCustomJSCLibrary);
      [self->_performanceLogger markStopForTag:RCTPLJSCWrapperOpenLibrary];

      RCTAssert(self->_context == nil, @"Didn't expect to set up twice");
      context = [self->_jscWrapper->JSContext new];
      self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
                                                          object:context];

      installBasicSynchronousHooksOnContext(context);
    }

    NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
    if (!threadDictionary[RCTFBJSContextClassKey] || !threadDictionary[RCTFBJSValueClassKey]) {
      threadDictionary[RCTFBJSContextClassKey] = self->_jscWrapper->JSContext;
      threadDictionary[RCTFBJSValueClassKey] = self->_jscWrapper->JSValue;
    }

    __weak RCTJSCExecutor *weakSelf = self;

    context[@"nativeRequireModuleConfig"] = ^NSArray *(NSString *moduleName) {
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid) {
        return nil;
      }

      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", @{ @"moduleName": moduleName });
      NSArray *result = [strongSelf->_bridge configForModuleName:moduleName];
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config");
      return RCTNullIfNil(result);
    };

    context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid || !calls) {
        return;
      }

      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeFlushQueueImmediate", nil);
      [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call");
    };

   

這里只說兩個重要回調(diào),一個是nativeRequireModuleConfig,一個是nativeFlushQueueImmediate。那么這兩個都有什么作用呢?先說nativeRequireModuleConfig,后面我們會知道,其實RN并沒有保存整個的OC方法配置表,而僅僅保存了模塊的名字。這個回調(diào)就是JS每次根據(jù)類名可以查到這個類的模塊配置信息。
然后就是nativeFlushQueueImmediate,一般來說JS并不會主動調(diào)用OC的方法,而是等著OC定時器每隔一段時間到JS的eventQueue中去取,取出來以后批量執(zhí)行。而nativeFlushQueueImmediate就是讓JS直接調(diào)用OC的方法而不用等待。
然后我們可以看到所有的調(diào)用都是handleBuffer這個方法來處理

      [strongSelf->_bridge handleBuffer:calls batchEnded:NO];

calls的話是一個數(shù)組,數(shù)組的格式我們可以打印出來看一下:

<__NSArrayM 0x28362e610>(
<__NSArrayM 0x28362d350>(
50
)
,
<__NSArrayM 0x28362e340>(
5
)
,
<__NSArrayM 0x28362d740>(
<__NSArrayM 0x28362cfc0>(
4,
RCTView,
1,
{
    flex = 1;
}
)

)
,
2
)

這是一個數(shù)組,數(shù)組的一個元素是模塊ID列表,第二個參數(shù)是方法ID列表,第三個就是參數(shù)列表。

然后就是moduleConfig和injectJSONConfiguration:

- (NSString *)moduleConfig
{
  NSMutableArray<NSArray *> *config = [NSMutableArray new];
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (self.executorClass == [RCTJSCExecutor class]) {
      [config addObject:@[moduleData.name]];
    } else {
      [config addObject:RCTNullIfNil(moduleData.config)];
    }
  }

  return RCTJSONStringify(@{
    @"remoteModuleConfig": config,
  }, NULL);
}

在之前也介紹過:moduleConfig把所有暴露給JS方法放到一個以remoteModuleConfig為key的字典里并序列化。
injectJSONConfiguration會把序列化好的字符串賦值給JS上下文的全局變量__fbBatchedBridgeConfig。
這樣JS里面就保存了所有OC暴露的方法名稱。

接下來我們就開始執(zhí)行JS代碼

dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
    RCTBatchedBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode];
    }
  });

二.RN執(zhí)行過程

我們發(fā)現(xiàn)真正執(zhí)行代碼的是RCTJSExecutor的executeApplicationScript函數(shù)。

static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, RCTJSCWrapper *jscWrapper,
                                         RCTPerformanceLogger *performanceLogger, JSGlobalContextRef ctx)
{
  RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / execute script", (@{
    @"url": sourceURL.absoluteString, @"size": @(script.length)
  }));
  [performanceLogger markStartForTag:RCTPLScriptExecution];
  JSValueRef jsError = NULL;
  JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)script.bytes);
  JSStringRef bundleURL = jscWrapper->JSStringCreateWithUTF8CString(sourceURL.absoluteString.UTF8String);
  jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError);
  jscWrapper->JSStringRelease(bundleURL);
  jscWrapper->JSStringRelease(execJSString);
  [performanceLogger markStopForTag:RCTPLScriptExecution];

  NSError *error = jsError ? RCTNSErrorFromJSErrorRef(jsError, ctx, jscWrapper) : nil;
  RCT_PROFILE_END_EVENT(0, @"js_call");
  return error;
}

通過代碼可以推測,真正的執(zhí)行者應(yīng)該是JSCWrapper->JSEvaluateScript。在這里執(zhí)行完畢之后,所有需要調(diào)用OC的事件都會被放到JS的eventQueue中。我們可以發(fā)現(xiàn)最后會回調(diào)到RCTJSCExecutor的flushedQueue方法。這個方法最后又會調(diào)到_executeJSCall,這是OC調(diào)用JS所執(zhí)行的方法。

- (void)_executeJSCall:(NSString *)method
             arguments:(NSArray *)arguments
          unwrapResult:(BOOL)unwrapResult
              callback:(RCTJavaScriptCallback)onComplete
{
  RCTAssert(onComplete != nil, @"onComplete block should not be nil");
  __weak RCTJSCExecutor *weakSelf = self;
  [self executeBlockOnJavaScriptQueue:^{
    RCTJSCExecutor *strongSelf = weakSelf;
    if (!strongSelf || !strongSelf.isValid) {
      return;
    }

    RCT_PROFILE_BEGIN_EVENT(0, @"executeJSCall", (@{@"method": method, @"args": arguments}));

    RCTJSCWrapper *jscWrapper = strongSelf->_jscWrapper;
    JSContext *context = strongSelf->_context.context;
    JSGlobalContextRef contextJSRef = context.JSGlobalContextRef;

    // get the BatchedBridge object
    JSValueRef errorJSRef = NULL;
    JSValueRef batchedBridgeRef = strongSelf->_batchedBridgeRef;
    if (!batchedBridgeRef) {
      JSStringRef moduleNameJSStringRef = jscWrapper->JSStringCreateWithUTF8CString("__fbBatchedBridge");
      JSObjectRef globalObjectJSRef = jscWrapper->JSContextGetGlobalObject(contextJSRef);
      batchedBridgeRef = jscWrapper->JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef);
      jscWrapper->JSStringRelease(moduleNameJSStringRef);
      strongSelf->_batchedBridgeRef = batchedBridgeRef;
    }

    NSError *error;
    JSValueRef resultJSRef = NULL;
    if (batchedBridgeRef != NULL && errorJSRef == NULL && !jscWrapper->JSValueIsUndefined(contextJSRef, batchedBridgeRef)) {
      // get method
      JSStringRef methodNameJSStringRef = jscWrapper->JSStringCreateWithCFString((__bridge CFStringRef)method);
      JSValueRef methodJSRef = jscWrapper->JSObjectGetProperty(contextJSRef, (JSObjectRef)batchedBridgeRef, methodNameJSStringRef, &errorJSRef);
      jscWrapper->JSStringRelease(methodNameJSStringRef);

      if (methodJSRef != NULL && errorJSRef == NULL && !jscWrapper->JSValueIsUndefined(contextJSRef, methodJSRef)) {
        JSValueRef jsArgs[arguments.count];
        for (NSUInteger i = 0; i < arguments.count; i++) {
          jsArgs[i] = [jscWrapper->JSValue valueWithObject:arguments[i] inContext:context].JSValueRef;
        }
        resultJSRef = jscWrapper->JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)batchedBridgeRef, arguments.count, jsArgs, &errorJSRef);
      } else {
        if (!errorJSRef && jscWrapper->JSValueIsUndefined(contextJSRef, methodJSRef)) {
          error = RCTErrorWithMessage([NSString stringWithFormat:@"Unable to execute JS call: method %@ is undefined", method]);
        }
      }
    } else {
      if (!errorJSRef && jscWrapper->JSValueIsUndefined(contextJSRef, batchedBridgeRef)) {
        error = RCTErrorWithMessage(@"Unable to execute JS call: __fbBatchedBridge is undefined");
      }
    }

    id objcValue;
    if (errorJSRef || error) {
      if (!error) {
        error = RCTNSErrorFromJSError([jscWrapper->JSValue valueWithJSValueRef:errorJSRef inContext:context]);
      }
    } else {
      // We often return `null` from JS when there is nothing for native side. [JSValue toValue]
      // returns [NSNull null] in this case, which we don't want.
      if (!jscWrapper->JSValueIsNull(contextJSRef, resultJSRef)) {
        JSValue *result = [jscWrapper->JSValue valueWithJSValueRef:resultJSRef inContext:context];
        objcValue = unwrapResult ? [result toObject] : result;
      }
    }

    RCT_PROFILE_END_EVENT(0, @"js_call");

    onComplete(objcValue, error);
  }];
}

flushedQueue就是從JS的eventQueue中獲取任務(wù),具體調(diào)用的是callFunctionReturnFlushedQueue這個方法,真正的調(diào)用者是_executeJSCall。以上調(diào)用只是初始化js的環(huán)境,并沒有真正執(zhí)行我們的業(yè)務(wù)代碼。
執(zhí)行完flushedQueue之后,這里會向主線程發(fā)送一個RCTJavaScriptDidLoadNotification通知,這個通知注冊在RCTRootView中。接到通知之后,RCTRootView首先會新建一個RCTRootContentView,這個view是所有我們創(chuàng)建的視圖的父視圖,它會被首先注冊在_viewRegistry(RN管理視圖的容器)tag為1,目的是表明view的層級是最底層。此外RN還會創(chuàng)建一個RCTRootShadowView以之對應(yīng),并且也會被加入一個容器_shadowViewRegistry里。詳細(xì)代碼在RCTUIManager的registerRootView方法。

- (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
{
  RCTAssertMainQueue();

  NSNumber *reactTag = rootView.reactTag;
  RCTAssert(RCTIsReactRootView(reactTag),
            @"View %@ with tag #%@ is not a root view", rootView, reactTag);

  UIView *existingView = _viewRegistry[reactTag];
  RCTAssert(existingView == nil || existingView == rootView,
            @"Expect all root views to have unique tag. Added %@ twice", reactTag);

  // Register view
  _viewRegistry[reactTag] = rootView;
  CGRect frame = rootView.frame;

  // Register shadow view
  dispatch_async(RCTGetUIManagerQueue(), ^{
    if (!self->_viewRegistry) {
      return;
    }

    RCTRootShadowView *shadowView = [RCTRootShadowView new];
    shadowView.reactTag = reactTag;
    shadowView.frame = frame;
    shadowView.backgroundColor = rootView.backgroundColor;
    shadowView.viewName = NSStringFromClass([rootView class]);
    shadowView.sizeFlexibility = sizeFlexibility;
    self->_shadowViewRegistry[shadowView.reactTag] = shadowView;
    [self->_rootViewTags addObject:reactTag];
  });

  [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification
                                                      object:self
                                                    userInfo:@{RCTUIManagerRootViewKey: rootView}];
}

在容器視圖初始化完成之后會發(fā)送一個通知,但是目前蘋果好像并沒有實現(xiàn)這個通知的接收者。
初始化容器視圖之后,就會調(diào)用 [self runApplication:bridge],這個方法開始執(zhí)行我們的業(yè)務(wù)代碼。

- (void)runApplication:(RCTBridge *)bridge
{
  NSString *moduleName = _moduleName ?: @"";
  NSDictionary *appParameters = @{
    @"rootTag": _contentView.reactTag,
    @"initialProps": _appProperties ?: @{},
  };

  RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
  [bridge enqueueJSCall:@"AppRegistry"
                 method:@"runApplication"
                   args:@[moduleName, appParameters]
             completion:NULL];
}

這里其實就會調(diào)用到RCTBatchedBridge的enqueueJSCall方法。內(nèi)部的話先會調(diào)用_actuallyInvokeAndProcessModule,這個方法會真正調(diào)用剛才我們所說的_executeJSCall方法,然后在這里設(shè)置了一個回調(diào),當(dāng)js執(zhí)行結(jié)束后會回調(diào)_processResponse方法并且把得到的調(diào)用用數(shù)組傳進(jìn)去。

- (void)_actuallyInvokeAndProcessModule:(NSString *)module
                                 method:(NSString *)method
                              arguments:(NSArray *)args
{
  RCTAssertJSThread();

  __weak __typeof(self) weakSelf = self;
  [_javaScriptExecutor callFunctionOnModule:module
                                     method:method
                                  arguments:args
                                   callback:^(id json, NSError *error) {
                                    //這里得到的json的格式我們之前有列出來
                                     [weakSelf _processResponse:json error:error];
                                   }];
}

這里就會把json(需要調(diào)用的原生方法數(shù)組)里面的moduleId,methodId,args取出來去調(diào)用callNativeModule。

[self callNativeModule:[moduleIDs[index] integerValue]
                          method:[methodIDs[index] integerValue]
                          params:paramsArrays[index]];

callNativeModule通過初始化時候創(chuàng)建的全局模塊信息,取出對象的類名,方法名,生成NSInvoke對象去調(diào)用相應(yīng)的方法。這里重點說一下UI對象。如果說是要創(chuàng)建UI對象的話,RN是直接調(diào)到UIManager createView方法(要注意這個方法是JS來調(diào)用的)。

createView:(nonnull NSNumber *)reactTag
                  viewName:(NSString *)viewName
                  rootTag:(__unused NSNumber *)rootTag
                  props:(NSDictionary *)props

這個方法里會首先根據(jù)全局字典獲得一個RCTComponentData,根據(jù)reactTag創(chuàng)建一個RCTShadowView并且把RCTShadowView和props(view的布局)關(guān)聯(lián)在RCTComponentData里。對View也是一樣的操作,并且會把View放到_bridgeTransactionListeners這個容器中。這個容器是干嘛的呢?稍后會介紹。
執(zhí)行完了createView方法,接下來會干什么?這里要注意,創(chuàng)建完了View有可能View里面還包含有子View。所以RN在這里判斷:如果有當(dāng)前的View有子View,會調(diào)用setChildren方法。

RCT_EXPORT_METHOD(setChildren:(nonnull NSNumber *)containerTag
                  reactTags:(NSArray<NSNumber *> *)reactTags)
{
//_shadowViewRegistry已經(jīng)在createView的時候把子View添加進(jìn)去了
  RCTSetChildren(containerTag, reactTags,
                 (NSDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry);

  [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){

    RCTSetChildren(containerTag, reactTags,
                   (NSDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry);
  }];
}

在這里要注意RCTSetChildren的操作。這里對于RCTShadowView的設(shè)置操作并不是真正的在View上做插入操作,而是放到內(nèi)部的一個全局?jǐn)?shù)組容器中。而真正的操作被放入了UIBlock等待執(zhí)行。
那么在子View也設(shè)置完成之后,代碼會往哪兒走呢?還記得我們是怎么調(diào)到設(shè)置子View的方法的嗎?是JS直接調(diào)用。而JS直接調(diào)用一開始會先調(diào)到handleBuffer方法,再來回顧一下這個方法。

- (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded
{
  RCTAssertJSThread();

  if (buffer != nil && buffer != (id)kCFNull) {
    _wasBatchActive = YES;
    [self handleBuffer:buffer];
    [self partialBatchDidFlush];
  }

  if (batchEnded) {
    if (_wasBatchActive) {
      [self batchDidComplete];
    }

    _wasBatchActive = NO;
  }
}

剛才我們只是調(diào)用完了[self handleBuffer:buffer]來處理JS調(diào)用,在這個調(diào)用結(jié)束后,還有一個[self batchDidComplete]操作,我們來看看這個方法具體做了什么:

- (void)batchDidComplete
{
  // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (moduleData.hasInstance && moduleData.implementsBatchDidComplete) {
      [self dispatchBlock:^{
        [moduleData.instance batchDidComplete];
      } queue:moduleData.methodQueue];
    }
  }
}

在每一個moduleData完成調(diào)用之后,會被標(biāo)記為implementsBatchDidComplete。這里會拿到已經(jīng)完成的moduleData. instance去調(diào)用batchDidComplete。還記得我們剛剛執(zhí)行的是哪一個方法么?剛剛我們執(zhí)行完成的是createView,createView是RCTUIManager的方法,所以這里的[moduleData.instance batchDidComplete]就會調(diào)到RCTUIManager的batchDidComplete。這個方法內(nèi)部會調(diào)用_layoutAndMount,在這個方法內(nèi)部會執(zhí)行剛才創(chuàng)建的UIBlock進(jìn)行UI布局。
接下來我們看看具體是怎么布局的:

- (void)_layoutAndMount
{
  // Gather blocks to be executed now that all view hierarchy manipulations have
  // been completed (note that these may still take place before layout has finished)
  for (RCTComponentData *componentData in _componentDataByName.allValues) {
    RCTViewManagerUIBlock uiBlock = [componentData uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
    [self addUIBlock:uiBlock];
  }

  // Perform layout
  for (NSNumber *reactTag in _rootViewTags) {
    RCTRootShadowView *rootView = (RCTRootShadowView *)_shadowViewRegistry[reactTag];
    [self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
    [self _amendPendingUIBlocksWithStylePropagationUpdateForShadowView:rootView];
  }

  [self addUIBlock:^(RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
    /**
     * TODO(tadeu): Remove it once and for all
     */
    for (id<RCTComponent> node in uiManager->_bridgeTransactionListeners) {
      [node reactBridgeDidFinishTransaction];
    }
  }];

  [self flushUIBlocks];
}

直接來看第二個for循環(huán),在[self uiBlockWithLayoutUpdateForRootView:rootView]方法中,我們會真正的計算出每一個RCTShadowView的坐標(biāo)以及其子View的坐標(biāo)。uiBlockWithLayoutUpdateForRootView的實現(xiàn)比較長,這里就不粘貼了。在這個方法一開始就會去計算所有RCTRootShadowView的坐標(biāo)。詳細(xì)的計算在[RCTRootShadowView collectViewsWithUpdatedFrames]。感興趣的同學(xué)可以看看RN是怎么實現(xiàn)的。計算完以后我們就會得到所有RCTRootShadowView的坐標(biāo),接下來這個方法會返回一個Block,這個Block里面會找到每一個RCTRootShadowView對應(yīng)的RCTView然后設(shè)置上frame,如果有動畫的話還會進(jìn)行動畫的執(zhí)行。這個Block會被裝進(jìn)UIBlock隊列中。
那么真正的subview是怎么添加的父view上的呢?真相就在[self _amendPendingUIBlocksWithStylePropagationUpdateForShadowView:rootView]這個方法調(diào)用里。

- (void)_amendPendingUIBlocksWithStylePropagationUpdateForShadowView:(RCTShadowView *)topView
{
  NSMutableSet<RCTApplierBlock> *applierBlocks = [NSMutableSet setWithCapacity:1];
  [topView collectUpdatedProperties:applierBlocks parentProperties:@{}];

  if (applierBlocks.count) {
    [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
      for (RCTApplierBlock block in applierBlocks) {
        block(viewRegistry);
      }
    }];
  }
}

首先在這里會創(chuàng)建一個block容器applierBlocks。我們會調(diào)用collectUpdatedProperties為容器添加一些block,并最終放到UI隊列里面去執(zhí)行。然后我們來看看collectUpdatedProperties具體做了什么操作:

- (void)collectUpdatedProperties:(NSMutableSet<RCTApplierBlock> *)applierBlocks
                parentProperties:(NSDictionary<NSString *, id> *)parentProperties
{
  if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
    return;
  }
  _propagationLifecycle = RCTUpdateLifecycleComputed;
  _lastParentProperties = parentProperties;
  NSDictionary<NSString *, id> *nextProps = [self processUpdatedProperties:applierBlocks parentProperties:parentProperties];
  for (RCTShadowView *child in _reactSubviews) {
    [child collectUpdatedProperties:applierBlocks parentProperties:nextProps];
  }
}

這里主要看[self processUpdatedProperties:applierBlocks parentProperties:parentProperties]這個方法。

- (NSDictionary<NSString *, id> *)processUpdatedProperties:(NSMutableSet<RCTApplierBlock> *)applierBlocks
                                          parentProperties:(NSDictionary<NSString *, id> *)parentProperties
{
  // TODO: we always refresh all propagated properties when propagation is
  // dirtied, but really we should track which properties have changed and
  // only update those.

  if (_didUpdateSubviews) {
    _didUpdateSubviews = NO;
    [self didUpdateReactSubviews];
    [applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
      UIView *view = viewRegistry[self->_reactTag];
      [view clearSortedSubviews];
      [view didUpdateReactSubviews];
    }];
  }

  if (!_backgroundColor) {
    UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp];
    if (parentBackgroundColor) {
      [applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
        UIView *view = viewRegistry[self->_reactTag];
        [view reactSetInheritedBackgroundColor:parentBackgroundColor];
      }];
    }
  } else {
    // Update parent properties for children
    NSMutableDictionary<NSString *, id> *properties = [NSMutableDictionary dictionaryWithDictionary:parentProperties];
    CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
    if (alpha < 1.0) {
      // If bg is non-opaque, don't propagate further
      properties[RCTBackgroundColorProp] = [UIColor clearColor];
    } else {
      properties[RCTBackgroundColorProp] = _backgroundColor;
    }
    return properties;
  }
  return parentProperties;
}

主要就看第一個if語句。這里會往applierBlocks添加一個block,這個block其實就會把當(dāng)前RCTShadowView相關(guān)聯(lián)的RCTView取出來,然后拿到相對應(yīng)的子View。我們主要看didUpdateReactSubviews這個方法。執(zhí)行這個方法其實會走到UIView的擴(kuò)展didUpdateReactSubviews方法里。

- (void)didUpdateReactSubviews
{
  for (UIView *subview in self.sortedReactSubviews) {
    [self addSubview:subview];
  }
}

拿到子View的關(guān)鍵就在這個sortedReactSubviews。通過函數(shù)調(diào)用鏈可以看到其實我們最終拿到子View是在

- (NSArray<UIView *> *)reactSubviews
{
  return objc_getAssociatedObject(self, _cmd);
}

方法里,然后我們看下這個關(guān)聯(lián)對象的設(shè)置位置:

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
  // We access the associated object directly here in case someone overrides
  // the `reactSubviews` getter method and returns an immutable array.
  NSMutableArray *subviews = objc_getAssociatedObject(self, @selector(reactSubviews));
  if (!subviews) {
    subviews = [NSMutableArray new];
    objc_setAssociatedObject(self, @selector(reactSubviews), subviews, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  [subviews insertObject:subview atIndex:atIndex];
}

同樣是在該擴(kuò)展的insertReactSubview方法。這個方法是不是很熟悉?還記得我們之前所說的JS調(diào)用完RCTUIManager的createView就會去調(diào)setChildren嗎?沒錯就是在setChildren的時候我們會調(diào)用到這里為UIView擴(kuò)展的關(guān)聯(lián)對象添加子View。
然后要記得所有的任務(wù)其實都是被放到了UI隊列里面去做的,所以在最后的話其實是會執(zhí)行UI隊列里的操作。

完!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容