最近的工作與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 <CommonCrypto/CommonDigest.h>
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 <NSURLProtocolClient>)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 <NSURLProtocolClient>)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 <kCacheExpireTime;
if (!bRet) {
// 過期刪除緩存
NSString *filename = [selfcachePathForRequest:[selfrequest]];
NSFileManager *defaultManager = [NSFileManagerdefaultManager];
if ([defaultManagerisDeletableFileAtPath:filename]) {
[defaultManager removeItemAtPath:filenameerror:nil];
}
}
return !bRet;
}
@end