本文是在看過一位大神的總結(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隊列里的操作。