從最外層源代碼來(lái)看陶耍,React Native初始化過(guò)程分為兩步:獲取JS代碼包的URL和創(chuàng)建RCTRootView實(shí)例,如下所示她混。
#import "AppDelegate.h"
#import "RCTBundleURLProvider.h"
#import "RCTRootView.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"AwesomeProject"
initialProperties:nil
launchOptions:launchOptions];
}
@end
其中烈钞,JS代碼包URL的獲取是通過(guò)RCTBundleProvider這個(gè)類(lèi)實(shí)現(xiàn)的泊碑。JS代碼包的URL有兩種可能結(jié)果,一種是Packager Server URL毯欣,一種是本地JS代碼包的文件路徑馒过。通過(guò)jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
去獲取JS代碼包URL時(shí),會(huì)首先檢查是否有正在運(yùn)行的Packager Server酗钞,如果有則返回相應(yīng)Packager Server中JS代碼包的絕對(duì)路徑腹忽;如果沒(méi)有正在運(yùn)行的Packager Server,則會(huì)返回一個(gè)本地JS代碼包的文件路徑砚作,不傳遞fallbackResource
參數(shù)時(shí)窘奏,默認(rèn)返回 本地main.jsbundle
的文件路徑。
接下來(lái)葫录,我們具體分析一下RCTBundleProvider的工作原理着裹。首先看看RCTBundleProvider提供的幾個(gè)重要方法。
serverRootWithHost
static NSURL *serverRootWithHost(NSString *host)
{
return [NSURL URLWithString:
[NSString stringWithFormat:@"http://%@:%lu/",
host, (unsigned long)kRCTBundleURLProviderDefaultPort]];
}
該方法會(huì)根據(jù)傳進(jìn)去的host返回相應(yīng)的Packager Server URL米同。例如骇扇,當(dāng)傳入的host為localhost時(shí),返回的URL為http://localhost:8081/面粮,其中8081是默認(rèn)端口號(hào)少孝,存儲(chǔ)在常量kRCTBundleURLProviderDefaultPort
中。
isPackagerRunning
- (BOOL)isPackagerRunning:(NSString *)host
{
NSURL *url = [serverRootWithHost(host) URLByAppendingPathComponent:@"status"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
NSString *status = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return [status isEqualToString:@"packager-status:running"];
}
該方法會(huì)判斷指定host上的Packager Server是否在運(yùn)行但金。首先韭山,通過(guò)serverRootWithHost
方法獲取該host的Packager Server URL,然后在Packager Server URL的末尾加上路徑status構(gòu)成新的URL冷溃,訪(fǎng)問(wèn)該URL钱磅,如果返回結(jié)果等于packager-status:running
,則證明指定host上的Packager Server正在運(yùn)行似枕。例如盖淡,當(dāng)傳入的host為localhost時(shí),訪(fǎng)問(wèn)http://localhost:8081/status凿歼,如果請(qǐng)求返回結(jié)果為packager-status:running
褪迟,則證明localhost上的Packager Server在運(yùn)行狀態(tài)。
guessPackagerHost
- (NSString *)guessPackagerHost
{
static NSString *ipGuess;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *ipPath = [[NSBundle mainBundle] pathForResource:@"ip" ofType:@"txt"];
ipGuess = [[NSString stringWithContentsOfFile:ipPath encoding:NSUTF8StringEncoding error:nil]
stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
});
NSString *host = ipGuess ?: @"localhost";
if ([self isPackagerRunning:host]) {
return host;
}
return nil;
}
這個(gè)方法比較有意思答憔,顧名思義味赃,就是猜測(cè)Packager Server的host。第一次訪(fǎng)問(wèn)該方法的時(shí)候虐拓,它會(huì)首先嘗試讀取本地的ip.txt文件心俗,并把文件內(nèi)容存進(jìn)靜態(tài)變量ipGuess
中。如果最后得到的ipGuess
的值不為空,則使用ipGuess
作為host城榛,否則默認(rèn)使用localhost作為host揪利。最后,還需要通過(guò)isPackagerRunning
方法判斷得到的host上是否有Packager Server在運(yùn)行狠持,如果有才返回該host作為Packager Host疟位,否則返回nil
。
ip.txt文件是在項(xiàng)目編譯運(yùn)行的時(shí)候通過(guò)react-native-xcode.sh
腳本生成的喘垂。在Debug模式下甜刻,react-native-xcode.sh
腳本會(huì)自動(dòng)獲取電腦網(wǎng)卡en0的ip地址并存進(jìn)ip.txt,如下所示王污。
if [[ "$CONFIGURATION" = "Debug" && "$PLATFORM_NAME" != "iphonesimulator" ]]; then
PLISTBUDDY='/usr/libexec/PlistBuddy'
PLIST=$TARGET_BUILD_DIR/$INFOPLIST_PATH
IP=$(ipconfig getifaddr en0)
$PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST"
$PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP.xip.io:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST"
echo "$IP.xip.io" > "$DEST/ip.txt"
fi
這樣罢吃,在使用iphone真機(jī)調(diào)試時(shí),只要保證iphone和電腦在同一局域網(wǎng)內(nèi)昭齐,iphone上的React Native程序即可從電腦上運(yùn)行的Packager Server加載JS代碼包尿招,無(wú)需再手動(dòng)設(shè)置jsCodeLocation
。
packagerServerHost
- (NSString *)packagerServerHost
{
NSString *location = [self jsLocation];
if (location != nil) {
return location;
}
#if RCT_DEV
NSString *host = [self guessPackagerHost];
if (host) {
return host;
}
#endif
return nil;
}
該方法用于獲取Packager Server Host阱驾。那既然有了guessPackagerHost
就谜,還要這個(gè)方法干什么呢?原來(lái)里覆,為了給使用者定制Packager Server Host的機(jī)會(huì)丧荐,RCTBundleProvider類(lèi)提供了jsLocation
屬性,通過(guò)設(shè)置jsLocation
屬性可以直接指定Packager Server Host喧枷。因此虹统,packagerServerHost
方法首先檢查jsLocation
屬性值,如果jsLocation
屬性值不為空隧甚,則返回 jsLocation
值作為Packager Server Host车荔,否則如果是Debug模式,則繼續(xù)通過(guò)guessPackagerHost
方法獲取Packager Server Host戚扳。以上方法都獲取不到Packager Server Host時(shí)忧便,則返回nil
。
packagerServerURL
- (NSURL *)packagerServerURL
{
NSString *const host = [self packagerServerHost];
return host ? serverRootWithHost(host) : nil;
}
該方法很簡(jiǎn)單帽借,首先通過(guò)packagerServerHost
方法獲取Packager Server Host珠增,然后通過(guò)serverRootWithHost
方法獲取Packager Server URL。
實(shí)例方法 jsBundleURLForBundleRoot
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackResource:(NSString *)resourceName
{
resourceName = resourceName ?: @"main";
NSString *packagerServerHost = [self packagerServerHost];
if (!packagerServerHost) {
return [[NSBundle mainBundle] URLForResource:resourceName withExtension:@"jsbundle"];
} else {
return [[self class] jsBundleURLForBundleRoot:bundleRoot
packagerHost:packagerServerHost
enableDev:[self enableDev]
enableMinification:[self enableMinification]];
}
}
該方法會(huì)返回最終的JS代碼包URL砍艾。它接收兩個(gè)參數(shù)蒂教,一個(gè)bundleRoot,指的是Packager Server中js代碼包的入口文件名脆荷。另一個(gè)是fallbackResource悴品,指的是本地JS代碼包的資源文件名,當(dāng)找不到Packager Server Host時(shí),會(huì)根據(jù)該資源文件名返回本地JS代碼包的文件路徑苔严。
類(lèi)方法 jsBundleURLForBundleRoot
+ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
packagerHost:(NSString *)packagerHost
enableDev:(BOOL)enableDev
enableMinification:(BOOL)enableMinification
{
NSURLComponents *components = [NSURLComponents componentsWithURL:serverRootWithHost(packagerHost) resolvingAgainstBaseURL:NO];
components.path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
// When we support only iOS 8 and above, use queryItems for a better API.
components.query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@",
enableDev ? @"true" : @"false",
enableMinification ? @"true": @"false"];
return components.URL;
}
該類(lèi)方法與實(shí)例方法jsBundleURLForBundleRoot
同名,但是該方法只用于生成Packager Server的JS代碼包URL孤澎。
介紹完RCTBundleProvider的重要方法届氢,最后簡(jiǎn)單梳理一下RCTBundleProvider的調(diào)用流程。
實(shí)例方法jsBundleURLForBundleRoot
作為入口方法被調(diào)用覆旭,在該方法里面退子,先嘗試獲取Packager Server Host。如果獲取到可用的Packager Server Host型将,則返回Packager Server中JS代碼包的URL作為jsCodeLocation
寂祥。如果沒(méi)有獲取到可用的Packager Server Host,則返回一個(gè)本地JS代碼包的文件路徑作為jsCodeLocation
七兜。