URL Loading System
在說 NSURLProtocol 之前需要對 URL Loading System 進(jìn)行說明,引用蘋果官方文檔對 URL Loading System 的一個解釋:
The URL loading system is a set of classes and protocols that allow your app to access content referenced by a URL. At the heart of this technology is the NSURL class, which lets your app manipulate URLs and the resources they refer to.
大致的意思就是 URL Loading System 是由一系列的 class 和 Protocol 組成,而我們可以通過這些 class 和 Protocol 來操作相關(guān)的 url ,其中處于核心的 class 就是 NSURL 悬而。
其中相關(guān)的 class 和 Protocol 可以使用官方的一張圖來說明:
當(dāng)然 URL Loading System 是由很多個方面組成的詳細(xì)的情況可以直接查詢蘋果的官方文檔 URL Loading System
URL loading system 原生已經(jīng)支持了http,https,file,ftp,data這些常見協(xié)議,當(dāng)然也允許我們定義自己的protocol去擴展峦朗,或者定義自己的協(xié)議份乒。當(dāng)URL loading system通過NSURLRequest對象進(jìn)行請求時,將會自動創(chuàng)建NSURLProtocol的實例(可以是自定義的)椰憋。這樣我們就有機會對該請求進(jìn)行處理厅克。官方文檔里面介紹得比較少,下面我們直接看如何自定義NSURLProtocol橙依,解讀一下 RNCachingURLProtocol這個開源庫的使用和原理证舟。
首先看一下源碼解析
//初始化
+ (void)initialize
{
if (self == [RNCachingURLProtocol class])
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RNCachingSupportedSchemesMonitor = [NSObject new];
});
//設(shè)置支持的協(xié)議類型
[self setSupportedSchemes:[NSSet setWithObject:@"http"]];
}
}
//是否可以處理此次的網(wǎng)絡(luò)請求 yes 可以 no 丟棄
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
// 判斷是否支持協(xié)議類型 和 request是否被處理過(防止遞歸調(diào)用)
if ([[self supportedSchemes] containsObject:[[request URL] scheme]] &&
([request valueForHTTPHeaderField:RNCachingURLHeader] == nil))
{
return YES;
}
return NO;
}
//這邊可用干你想干的事情。窗骑。更改地址女责,或者設(shè)置里面的請求頭。创译。
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
//設(shè)置請求內(nèi)容的緩存地址
- (NSString *)cachePathForRequest:(NSURLRequest *)aRequest
{
// This stores in the Caches directory, which can be deleted when space is low, but we only use it for offline access
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [[[aRequest URL] absoluteString] sha1];//這里只是一個用SHA1算法將字符串加密的category
return [cachesPath stringByAppendingPathComponent:fileName];
}
- (void)startLoading
{
//判斷是否已經(jīng)緩存過
if (![self useCache]) {
NSMutableURLRequest *connectionRequest =
#if WORKAROUND_MUTABLE_COPY_LEAK
[[self request] mutableCopyWorkaround];
#else
[[self request] mutableCopy];
#endif
// 打一下標(biāo)記
[connectionRequest setValue:@"" forHTTPHeaderField:RNCachingURLHeader];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest
delegate:self];
[self setConnection:connection];
}
else {
//創(chuàng)建緩存對象
RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:[self cachePathForRequest:[self request]]];
if (cache) {
NSData *data = [cache data];
NSURLResponse *response = [cache response];
NSURLRequest *redirectRequest = [cache redirectRequest];
if (redirectRequest) {
//重復(fù)訪問了同一個請求
[[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
} else {
//處理請求
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // we handle caching ourselves.
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}
}
else {
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]];
}
}
}
//緩存請求的內(nèi)容
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
// Thanks to Nick Dowell https://gist.github.com/1885821
if (response != nil) {
NSMutableURLRequest *redirectableRequest =
#if WORKAROUND_MUTABLE_COPY_LEAK
[request mutableCopyWorkaround];
#else
[request mutableCopy];
#endif
//標(biāo)記為nil 防止遞歸調(diào)用 canonicalRequestForRequest
[redirectableRequest setValue:nil forHTTPHeaderField:RNCachingURLHeader];
NSString *cachePath = [self cachePathForRequest:[self request]];
RNCachedData *cache = [RNCachedData new];
[cache setResponse:response];
[cache setData:[self data]];
[cache setRedirectRequest:redirectableRequest];
[NSKeyedArchiver archiveRootObject:cache toFile:cachePath];
[[self client] URLProtocol:self wasRedirectedToRequest:redirectableRequest redirectResponse:response];
return redirectableRequest;
} else {
return request;
}
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[[self client] URLProtocol:self didLoadData:data];
[self appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[[self client] URLProtocol:self didFailWithError:error];
[self setConnection:nil];
[self setData:nil];
[self setResponse:nil];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self setResponse:response];
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // We cache ourselves.
}
注意點:
每次只能只有一個protocol進(jìn)行處理抵知,如果有多個自定義protocol,系統(tǒng)將采取你registerClass的倒序進(jìn)行調(diào)用,一旦你需要對這個請求進(jìn)行處理刷喜,那么接下來的所有相關(guān)操作都需要這個protocol進(jìn)行管理残制。
一定要注意標(biāo)記請求,不然你會無限的循環(huán)下去掖疮。初茶。。因為一旦你需要處理這個請求浊闪,那么系統(tǒng)會創(chuàng)建你這個protocol的實例恼布,然后你自己又開啟了connection進(jìn)行請求的話,又會觸發(fā)URL Loading system的回調(diào)搁宾。系統(tǒng)給我們提供了+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;和+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;這兩個方法進(jìn)行標(biāo)記和區(qū)分折汞。
大家在使用的時候只需要在Appdelegate注冊一下,然后緩存路徑自己處理一下就可以了。
[NSURLProtocol registerClass:[RNCachingURLProtocol class]];
歡迎討論!