URL Loading System
在说 NSURLProtocol 之前需要对 URL Loading System 进行说明,引用苹果官方文档对 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 来操作相关的 url ,其中处于核心的 class 就是 NSURL 。
其中相关的 class 和 Protocol 可以使用官方的一张图来说明:
当然 URL Loading System 是由很多个方面组成的详细的情况可以直接查询苹果的官方文档 URL Loading System
URL loading system 原生已经支持了http,https,file,ftp,data这些常见协议,当然也允许我们定义自己的protocol去扩展,或者定义自己的协议。当URL loading system通过NSURLRequest对象进行请求时,将会自动创建NSURLProtocol的实例(可以是自定义的)。这样我们就有机会对该请求进行处理。官方文档里面介绍得比较少,下面我们直接看如何自定义NSURLProtocol,解读一下 RNCachingURLProtocol这个开源库的使用和原理。
首先看一下源码解析
//初始化
+ (void)initialize
{
if (self == [RNCachingURLProtocol class])
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RNCachingSupportedSchemesMonitor = [NSObject new];
});
//设置支持的协议类型
[self setSupportedSchemes:[NSSet setWithObject:@"http"]];
}
}
//是否可以处理此次的网络请求 yes 可以 no 丢弃
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
// 判断是否支持协议类型 和 request是否被处理过(防止递归调用)
if ([[self supportedSchemes] containsObject:[[request URL] scheme]] &&
([request valueForHTTPHeaderField:RNCachingURLHeader] == nil))
{
return YES;
}
return NO;
}
//这边可用干你想干的事情。。更改地址,或者设置里面的请求头。。
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
//设置请求内容的缓存地址
- (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
{
//判断是否已经缓存过
if (![self useCache]) {
NSMutableURLRequest *connectionRequest =
#if WORKAROUND_MUTABLE_COPY_LEAK
[[self request] mutableCopyWorkaround];
#else
[[self request] mutableCopy];
#endif
// 打一下标记
[connectionRequest setValue:@"" forHTTPHeaderField:RNCachingURLHeader];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest
delegate:self];
[self setConnection:connection];
}
else {
//创建缓存对象
RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:[self cachePathForRequest:[self request]]];
if (cache) {
NSData *data = [cache data];
NSURLResponse *response = [cache response];
NSURLRequest *redirectRequest = [cache redirectRequest];
if (redirectRequest) {
//重复访问了同一个请求
[[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]];
}
}
}
//缓存请求的内容
- (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
//标记为nil 防止递归调用 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进行处理,如果有多个自定义protocol,系统将采取你registerClass的倒序进行调用,一旦你需要对这个请求进行处理,那么接下来的所有相关操作都需要这个protocol进行管理。
一定要注意标记请求,不然你会无限的循环下去。。。因为一旦你需要处理这个请求,那么系统会创建你这个protocol的实例,然后你自己又开启了connection进行请求的话,又会触发URL Loading system的回调。系统给我们提供了+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;和+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;这两个方法进行标记和区分。
大家在使用的时候只需要在Appdelegate注册一下,然后缓存路径自己处理一下就可以了。
[NSURLProtocol registerClass:[RNCachingURLProtocol class]];
欢迎讨论!