最近的工作与UIWebView太相关了,索性把这方面的技术都写到博客里来;
我提供的这个版本的NSURLProtocol是基于NSURLSession类来做的(NSURLConnection类在9.0之后就会被遗弃)。
支持的功能:
1、缓存URL的请求数据;
2、支持缓存的过期处理;
3、支持配置过滤URL,注意,NSURLProtocol这个类一旦被注册,就是属于APP级别的协议类,他会监听所有的http以及https的请求。所以很多的时候我们需要设置一个过滤url数组,这个过滤url只需要提供url前缀即可(当然你不设置理论上也没有关系都走一次监听而已)
code:
//
// CacheURLProtocol.m
// CommonProject
//
// Created by wuyoujian on 16/6/2.
// Copyright © 2016年 wuyoujian. All rights reserved.
//
#import "CacheURLProtocol.h"
#import
static NSUIntegerconst kCacheExpireTime = 600;//缓存的时间 默认设置为600秒
@interface WebCachedData :NSObject <NSCoding>
@property (nonatomic,strong) NSData *data;
@property (nonatomic,strong) NSURLResponse *response;
@property (nonatomic,strong) NSURLRequest *redirectRequest;
@property (nonatomic,strong) NSDate *date;
@end
static NSString *const kDataKey =@"data";
static NSString *const kResponseKey =@"response";
static NSString *const kRedirectRequestKey =@"redirectRequest";
static NSString *const kDateKey =@"date";
@implementation WebCachedData
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:[selfdata] forKey:kDataKey];
[aCoder encodeObject:[selfresponse] forKey:kResponseKey];
[aCoder encodeObject:[selfredirectRequest] forKey:kRedirectRequestKey];
[aCoder encodeObject:[selfdate] forKey:kDateKey];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [superinit];
if (self !=nil) {
[selfsetData:[aDecoder decodeObjectForKey:kDataKey]];
[selfsetResponse:[aDecoder decodeObjectForKey:kResponseKey]];
[selfsetRedirectRequest:[aDecoder decodeObjectForKey:kRedirectRequestKey]];
[selfsetDate:[aDecoder decodeObjectForKey:kDateKey]];
}
returnself;
}
@end
static NSString *const kOurRecursiveRequestFlagProperty =@"COM.WEIMEITC.CACHE";
static NSString *const kSessionQueueName =@"WEIMEITC_SESSIONQUEUENAME";
static NSString *const kSessionDescription =@"WEIMEITC_SESSIONDESCRIPTION";
@interface CacheURLProtocol ()<NSURLSessionDataDelegate>
@property (nonatomic,strong) NSURLSession *session;
@property (nonatomic,copy) NSURLSessionConfiguration *configuration;
@property (nonatomic,strong) NSOperationQueue *sessionQueue;
@property (nonatomic,strong) NSURLSessionDataTask *task;
@property (nonatomic,strong) NSMutableData *data;
@property (nonatomic,strong) NSURLResponse *response;
- (void)appendData:(NSData *)newData;
@end
@implementation CacheURLProtocol
static NSObject *CacheURLProtocolIgnoreURLsMonitor;
static NSArray *CacheURLProtocolIgnoreURLs;
+ (BOOL)registerProtocolWithIgnoreURLs:(NSArray*)ignores {
[selfunregisterCacheURLProtocol];
[selfsetIgnoreURLs:ignores];
return [[selfclass] registerClass:[selfclass]];
}
+ (void)unregisterCacheURLProtocol {
[selfsetIgnoreURLs:nil];
[[selfclass] unregisterClass:[selfclass]];
}
- (void)dealloc {
[self.taskcancel];
[selfsetTask:nil];
[selfsetSession:nil];
[selfsetData:nil];
[selfsetResponse:nil];
[selfsetSessionQueue:nil];
[selfsetConfiguration:nil];
[[selfclass] setIgnoreURLs:nil];
}
+(void)initialize {
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CacheURLProtocolIgnoreURLsMonitor = [NSObjectnew];
});
}
#pragma mark - URLProtocol APIs
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
// 可以修改request对象
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [superrequestIsCacheEquivalent:a toRequest:b];
}
+ (BOOL)canInitWithTask:(NSURLSessionTask *)task {
return [selfcanInitWithURLRequest:task.currentRequest];
}
- (instancetype)initWithTask:(NSURLSessionTask *)task cachedResponse:(nullableNSCachedURLResponse *)cachedResponse client:(nullableid )client {
self = [superinitWithTask:task cachedResponse:cachedResponseclient:client];
if (self !=nil) {
[selfconfigProtocolParam];
}
returnself;
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return [selfcanInitWithURLRequest:request];
}
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id )client {
self = [superinitWithRequest:request cachedResponse:cachedResponseclient:client];
if (self !=nil) {
[selfconfigProtocolParam];
}
returnself;
}
- (void)startLoading {
WebCachedData *cache = [NSKeyedUnarchiverunarchiveObjectWithFile:[selfcachePathForRequest:[selfrequest]]];
// 这地方不能判断cache.data字段,有可能是一个重定向的request
if (cache) {
// 本地有缓存
NSData *data = [cachedata];
NSURLResponse *response = [cacheresponse];
NSURLRequest *redirectRequest = [cacheredirectRequest];
NSDate *date = [cachedate];
if ([selfexpireCacheData:date]) {
// 数据过期
NSLog(@"request Data-expire!");
NSMutableURLRequest *recursiveRequest = [[selfrequest] mutableCopy];
[[selfclass] setProperty:@YESforKey:kOurRecursiveRequestFlagPropertyinRequest:recursiveRequest];
self.task = [self.sessiondataTaskWithRequest:recursiveRequest];
[self.taskresume];
} else {
if (redirectRequest) {
[[selfclient] URLProtocol:selfwasRedirectedToRequest:redirectRequestredirectResponse:response];
} else {
if (data) {
NSLog(@"cached Data!");
[[selfclient] URLProtocol:selfdidReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[selfclient] URLProtocol:selfdidLoadData:data];
[[selfclient] URLProtocolDidFinishLoading:self];
} else {
// 本地没有缓存上data
NSLog(@"request Data-uncached data!");
NSMutableURLRequest *recursiveRequest = [[selfrequest] mutableCopy];
[[selfclass] setProperty:@YESforKey:kOurRecursiveRequestFlagPropertyinRequest:recursiveRequest];
self.task = [self.sessiondataTaskWithRequest:recursiveRequest];
[self.taskresume];
}
}
}
} else {
// 本地无缓存
NSLog(@"request Data-no data!");
NSMutableURLRequest *recursiveRequest = [[selfrequest] mutableCopy];
[[selfclass] setProperty:@YESforKey:kOurRecursiveRequestFlagPropertyinRequest:recursiveRequest];
self.task = [self.sessiondataTaskWithRequest:recursiveRequest];
[self.taskresume];
}
}
- (void)stopLoading {
[self.taskcancel];
[selfsetTask:nil];
[selfsetData:nil];
[selfsetResponse:nil];
}
#pragma mark - NSURLSession delegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler {
if (response !=nil) {
NSMutableURLRequest *redirectableRequest = [newRequestmutableCopy];
[[selfclass] removePropertyForKey:kOurRecursiveRequestFlagPropertyinRequest:redirectableRequest];
NSString *cachePath = [selfcachePathForRequest:[selfrequest]];
WebCachedData *cache = [[WebCachedDataalloc] init];
[cache setResponse:response];
[cache setData:[selfdata]];
[cache setDate:[NSDatedate]];
[cache setRedirectRequest:redirectableRequest];
[NSKeyedArchiverarchiveRootObject:cache toFile:cachePath];
[[selfclient] URLProtocol:selfwasRedirectedToRequest:redirectableRequestredirectResponse:response];
[self.taskcancel];
[[selfclient] URLProtocol:selfdidFailWithError:[NSErrorerrorWithDomain:NSCocoaErrorDomaincode:NSUserCancelledErroruserInfo:nil]];
completionHandler(redirectableRequest);
} else {
completionHandler(newRequest);
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void(^)(NSURLSessionResponseDisposition))completionHandler {
[selfsetResponse:response];
[selfsetData:nil];
[[selfclient] URLProtocol:selfdidReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[[selfclient] URLProtocol:selfdidLoadData:data];
[selfappendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)dataTask didCompleteWithError:(NSError *)error {
if (error ) {
[self.clientURLProtocol:selfdidFailWithError:error];
} else {
NSString *cachePath = [selfcachePathForRequest:[selfrequest]];
WebCachedData *cache = [[WebCachedDataalloc] init];
[cache setResponse:[selfresponse]];
[cache setData:[selfdata]];
[cache setDate:[NSDatedate]];
[NSKeyedArchiverarchiveRootObject:cache toFile:cachePath];
[[selfclient] URLProtocolDidFinishLoading:self];
}
}
#pragma mark - private APIs
+ (NSArray *)ignoreURLs {
NSArray *iURLs;
@synchronized(CacheURLProtocolIgnoreURLsMonitor) {
iURLs = CacheURLProtocolIgnoreURLs;
}
return iURLs;
}
+ (void)setIgnoreURLs:(NSArray *)iURLs {
@synchronized(CacheURLProtocolIgnoreURLsMonitor) {
CacheURLProtocolIgnoreURLs = iURLs;
}
}
+ (BOOL)canInitWithURLRequest:(NSURLRequest*)request {
// 过滤掉不需要走URLProtocol
NSArray *ignores = [selfignoreURLs];
for (NSString *urlin ignores) {
if ([[request.URLabsoluteString] hasPrefix:url]) {
returnNO;
}
}
// 如果是startLoading里发起的request忽略掉,避免死循环
BOOL recurisve = [selfpropertyForKey:kOurRecursiveRequestFlagPropertyinRequest:request] == nil;
// 没有标识位kOurRecursiveRequestFlagProperty的并且是以http开的scheme都走代理;
if (recurisve && [[request.URLscheme] hasPrefix:@"http"]) {
returnYES;
}
returnNO;
}
- (void)configProtocolParam {
NSURLSessionConfiguration *config = [[NSURLSessionConfigurationdefaultSessionConfiguration] copy];
[config setProtocolClasses:@[ [selfclass] ]];
[selfsetConfiguration:config];
NSOperationQueue *q = [[NSOperationQueuealloc] init];
[q setMaxConcurrentOperationCount:1];
[q setName:kSessionQueueName];
[selfsetSessionQueue:q];
NSURLSession *s = [NSURLSessionsessionWithConfiguration:_configurationdelegate:selfdelegateQueue:_sessionQueue];
s.sessionDescription =kSessionDescription;
[selfsetSession:s];
}
- (NSString*)md5Encode:(NSString*)srcString {
constchar *cStr = [srcString UTF8String];
unsignedchar result[16];
CC_MD5( cStr, (unsignedint)strlen(cStr), result);
NSString *formatString =@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
return [NSStringstringWithFormat:formatString,
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]];
}
- (NSString *)cachePathForRequest:(NSURLRequest *)aRequest {
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES)lastObject];
NSString *fileName = [selfmd5Encode:[[aRequest URL]absoluteString]];
return [cachesPathstringByAppendingPathComponent:fileName];
}
- (void)appendData:(NSData *)newData {
if ([selfdata] == nil) {
self.data = [[NSMutableDataalloc] initWithCapacity:0];
}
if (newData) {
[[selfdata] appendData:newData];
}
}
- (BOOL)expireCacheData:(NSDate *)date {
if (!date) {
returnYES;
}
NSTimeInterval timeInterval = [[NSDatedate] timeIntervalSinceDate:date];
BOOL bRet = timeInterval if (!bRet) {
// 过期删除缓存
NSString *filename = [selfcachePathForRequest:[selfrequest]];
NSFileManager *defaultManager = [NSFileManagerdefaultManager];
if ([defaultManagerisDeletableFileAtPath:filename]) {
[defaultManager removeItemAtPath:filenameerror:nil];
}
}
return !bRet;
}
@end