非常详细!!React-Native iOS源码阅读纪录

本文是在看过一位大神的总结之后,又亲自根据文章看了一遍源码,算是对大神的文章做一些补充。下面给出文章链接。阅读本文最好参考文章的函数调用顺序以及源码会更加明朗。
http://www.cocoachina.com/programmer/20170505/19189.html

一.RN初始化过程

React-Native本身是通过iOS提供的JavaScriptCore框架进行通信的。
下面我们来看一下加载流程。首先找到RN的入口,也就是创建RCTRootView的地方:

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

这个方法内部会调到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对象并且会调用另外一个构造函数,我们先来看RCTBridge初始化的时候做了什么?顺着方法调用链,我们来到了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, @"");
}

在这个函数里我们发现RN会创建一个RCTBatchedBridge,并且把RCTBatchedBridge作为自己的一个属性。在RCTBatchedBridge内部会把当前的RCTBridge对象作为一个全局静态属性。
接下来就会执行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];
}

首先会触发一个RCTJavaScriptWillStartLoadingNotification通知,从通知命名一看便知这里是告诉我们即将要加载JavaScript代码。然后就会去调用loadSource方法,要注意这里loadSource有同步加载和异步加载两种情况,如果同步加载失败就会去调用异步加载的方法,最后加载得到的是一个NSData对象。
接下来会调用initModulesWithDispatchGroup方法并且把一个GCD group作为参数传进去,通过名字便可知这个方法是做一些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的类都装到数组里并返回,然后根据moduleName创建ModuleData并缓存到全局的字典中。还会把moduleClass放到moduleClassesByID,moduleData放到moduleDataByID两个实例数组中。到这里所有要暴露给JS的类和方法都被装到了RCTBatchedBridge的实例容器中。这里我要补充下,ModuleData里面有一个requiresMainQueueSetup表明当前模块是否在主线程执行,这样就避免了UI事件在子线程中执行。
moduleData准备完毕之后,还会做一些初始化js执行器等等操作,并且把配置表写入JS的操作,这里用到了dispatch_group来控制当javascriptExcutor初始化完毕,配置表加载完毕之后再执行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,在这里会调到RCTJSCExecutor setUp方法。在setUp主要看调用了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");
    };

   

这里只说两个重要回调,一个是nativeRequireModuleConfig,一个是nativeFlushQueueImmediate。那么这两个都有什么作用呢?先说nativeRequireModuleConfig,后面我们会知道,其实RN并没有保存整个的OC方法配置表,而仅仅保存了模块的名字。这个回调就是JS每次根据类名可以查到这个类的模块配置信息。
然后就是nativeFlushQueueImmediate,一般来说JS并不会主动调用OC的方法,而是等着OC定时器每隔一段时间到JS的eventQueue中去取,取出来以后批量执行。而nativeFlushQueueImmediate就是让JS直接调用OC的方法而不用等待。
然后我们可以看到所有的调用都是handleBuffer这个方法来处理

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

calls的话是一个数组,数组的格式我们可以打印出来看一下:

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

)
,
2
)

这是一个数组,数组的一个元素是模块ID列表,第二个参数是方法ID列表,第三个就是参数列表。

然后就是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暴露的方法名称。

接下来我们就开始执行JS代码

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

二.RN执行过程

我们发现真正执行代码的是RCTJSExecutor的executeApplicationScript函数。

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

通过代码可以推测,真正的执行者应该是JSCWrapper->JSEvaluateScript。在这里执行完毕之后,所有需要调用OC的事件都会被放到JS的eventQueue中。我们可以发现最后会回调到RCTJSCExecutor的flushedQueue方法。这个方法最后又会调到_executeJSCall,这是OC调用JS所执行的方法。

- (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中获取任务,具体调用的是callFunctionReturnFlushedQueue这个方法,真正的调用者是_executeJSCall。以上调用只是初始化js的环境,并没有真正执行我们的业务代码。
执行完flushedQueue之后,这里会向主线程发送一个RCTJavaScriptDidLoadNotification通知,这个通知注册在RCTRootView中。接到通知之后,RCTRootView首先会新建一个RCTRootContentView,这个view是所有我们创建的视图的父视图,它会被首先注册在_viewRegistry(RN管理视图的容器)tag为1,目的是表明view的层级是最底层。此外RN还会创建一个RCTRootShadowView以之对应,并且也会被加入一个容器_shadowViewRegistry里。详细代码在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}];
}

在容器视图初始化完成之后会发送一个通知,但是目前苹果好像并没有实现这个通知的接收者。
初始化容器视图之后,就会调用 [self runApplication:bridge],这个方法开始执行我们的业务代码。

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

这里其实就会调用到RCTBatchedBridge的enqueueJSCall方法。内部的话先会调用_actuallyInvokeAndProcessModule,这个方法会真正调用刚才我们所说的_executeJSCall方法,然后在这里设置了一个回调,当js执行结束后会回调_processResponse方法并且把得到的调用用数组传进去。

- (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(需要调用的原生方法数组)里面的moduleId,methodId,args取出来去调用callNativeModule。

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

callNativeModule通过初始化时候创建的全局模块信息,取出对象的类名,方法名,生成NSInvoke对象去调用相应的方法。这里重点说一下UI对象。如果说是要创建UI对象的话,RN是直接调到UIManager createView方法(要注意这个方法是JS来调用的)。

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

这个方法里会首先根据全局字典获得一个RCTComponentData,根据reactTag创建一个RCTShadowView并且把RCTShadowView和props(view的布局)关联在RCTComponentData里。对View也是一样的操作,并且会把View放到_bridgeTransactionListeners这个容器中。这个容器是干嘛的呢?稍后会介绍。
执行完了createView方法,接下来会干什么?这里要注意,创建完了View有可能View里面还包含有子View。所以RN在这里判断:如果有当前的View有子View,会调用setChildren方法。

RCT_EXPORT_METHOD(setChildren:(nonnull NSNumber *)containerTag
                  reactTags:(NSArray<NSNumber *> *)reactTags)
{
//_shadowViewRegistry已经在createView的时候把子View添加进去了
  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的设置操作并不是真正的在View上做插入操作,而是放到内部的一个全局数组容器中。而真正的操作被放入了UIBlock等待执行。
那么在子View也设置完成之后,代码会往哪儿走呢?还记得我们是怎么调到设置子View的方法的吗?是JS直接调用。而JS直接调用一开始会先调到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;
  }
}

刚才我们只是调用完了[self handleBuffer:buffer]来处理JS调用,在这个调用结束后,还有一个[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完成调用之后,会被标记为implementsBatchDidComplete。这里会拿到已经完成的moduleData. instance去调用batchDidComplete。还记得我们刚刚执行的是哪一个方法么?刚刚我们执行完成的是createView,createView是RCTUIManager的方法,所以这里的[moduleData.instance batchDidComplete]就会调到RCTUIManager的batchDidComplete。这个方法内部会调用_layoutAndMount,在这个方法内部会执行刚才创建的UIBlock进行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循环,在[self uiBlockWithLayoutUpdateForRootView:rootView]方法中,我们会真正的计算出每一个RCTShadowView的坐标以及其子View的坐标。uiBlockWithLayoutUpdateForRootView的实现比较长,这里就不粘贴了。在这个方法一开始就会去计算所有RCTRootShadowView的坐标。详细的计算在[RCTRootShadowView collectViewsWithUpdatedFrames]。感兴趣的同学可以看看RN是怎么实现的。计算完以后我们就会得到所有RCTRootShadowView的坐标,接下来这个方法会返回一个Block,这个Block里面会找到每一个RCTRootShadowView对应的RCTView然后设置上frame,如果有动画的话还会进行动画的执行。这个Block会被装进UIBlock队列中。
那么真正的subview是怎么添加的父view上的呢?真相就在[self _amendPendingUIBlocksWithStylePropagationUpdateForShadowView:rootView]这个方法调用里。

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

首先在这里会创建一个block容器applierBlocks。我们会调用collectUpdatedProperties为容器添加一些block,并最终放到UI队列里面去执行。然后我们来看看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其实就会把当前RCTShadowView相关联的RCTView取出来,然后拿到相对应的子View。我们主要看didUpdateReactSubviews这个方法。执行这个方法其实会走到UIView的扩展didUpdateReactSubviews方法里。

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

拿到子View的关键就在这个sortedReactSubviews。通过函数调用链可以看到其实我们最终拿到子View是在

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

方法里,然后我们看下这个关联对象的设置位置:

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

同样是在该扩展的insertReactSubview方法。这个方法是不是很熟悉?还记得我们之前所说的JS调用完RCTUIManager的createView就会去调setChildren吗?没错就是在setChildren的时候我们会调用到这里为UIView扩展的关联对象添加子View。
然后要记得所有的任务其实都是被放到了UI队列里面去做的,所以在最后的话其实是会执行UI队列里的操作。

完!!!

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

推荐阅读更多精彩内容