项目需求: 页面需要有缓存功能,有网络的时候直接请求服务器,没有网络的时候从缓存获取。
1.应用webviewcache组件
TURLSessionProtocol.h
TURLSessionProtocol.m
2. 注册缓存组件
在Appdelegate中的application:didFinishLaunchingWithOptions:方法中加上:
[NSURLProtocol registerClass:[TURLSessionProtocol class]];
3.webView加载url即可
NSString * webUrl=@"http://localhost:8080/html/";
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:webUrl]];
[self.webView loadRequest:request];
以下是TURLSessionProtocol源码
==============TURLSessionProtocol.h===============
#import <Foundation/Foundation.h>
extern NSString *const KProtocolHttpHeadKey;
@interface TURLSessionProtocol :NSURLProtocol
//添加需要过滤的请求前缀
+ (NSSet *)filterUrlPres;
+ (void)setFilterUrlPres:(NSSet *)filterUrlPre;
@end
=============TURLSessionProtocol.m==============
#import "TURLSessionProtocol.h"
#import <objc/runtime.h>
#import <CommonCrypto/CommonDigest.h>
#import "AppDelegate.h"
#import "BWAFNetWorkingTools.h"
NSString *const KProtocolHttpHeadKey =@"KProtocolHttpHeadKey";
static NSUIntegerconst KCacheTime = 113529600000;//365*24*3600;//缓存的时间 默认设置为360秒可以任意的更改
static NSObject *TURLSessionFilterUrlPreObject;
static NSSet *TURLSessionFilterUrlPre;
@interface NSURLRequest(MutableCopyWorkaround)
- (id)mutableCopyWorkaround;
@end
@interface NSString (MD5)
- (NSString *)md5String;
@end
@interface TURLProtocolCacheData :NSObject<NSCoding>
@property (nonatomic,strong) NSDate *addDate;
@property (nonatomic,strong) NSData *data;
@property (nonatomic,strong) NSURLResponse *response;
@property (nonatomic,strong) NSURLRequest *redirectRequest;
@end
@interface TURLSessionProtocol ()<NSURLSessionDataDelegate>
@property (nonatomic,strong) NSURLSession *session;
@property (nonatomic,strong) NSURLSessionDataTask *downloadTask;
@property (nonatomic,strong) NSURLResponse *response;
@property (nonatomic,strong) NSMutableData *cacheData;
@end
@implementation NSURLRequest (MutableCopyWorkaround)
-(id)mutableCopyWorkaround {
NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequestalloc] initWithURL:[selfURL]
cachePolicy:[selfcachePolicy]
timeoutInterval:[selftimeoutInterval]];
[mutableURLRequest setAllHTTPHeaderFields:[selfallHTTPHeaderFields]];
if ([selfHTTPBodyStream]) {
[mutableURLRequest setHTTPBodyStream:[selfHTTPBodyStream]];
} else {
[mutableURLRequest setHTTPBody:[selfHTTPBody]];
}
[mutableURLRequest setHTTPMethod:[selfHTTPMethod]];
return mutableURLRequest;
}
@end
@implementation NSString(MD5)
- (NSString *)md5String {
NSData *data = [selfdataUsingEncoding:NSUTF8StringEncoding];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, data.length, digest);
NSMutableString *output = [NSMutableStringstringWithCapacity:CC_SHA1_DIGEST_LENGTH *2];
for (int i =0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x", digest[i]];
}
return output;
}
@end
@implementation TURLProtocolCacheData
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsignedint count;
Ivar *ivar =class_copyIvarList([selfclass], &count);
for (int i =0 ; i < count ; i++) {
Ivar iv = ivar[i];
constchar *name = ivar_getName(iv);
NSString *strName = [NSStringstringWithUTF8String:name];
//利用KVC取值
id value = [selfvalueForKey:strName];
[aCoder encodeObject:valueforKey:strName];
}
free(ivar);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [superinit];
if (self !=nil) {
unsignedint count = 0;
Ivar *ivar =class_copyIvarList([selfclass], &count);
for (int i=0 ;i < count ; i++) {
Ivar var = ivar[i];
constchar *keyName = ivar_getName(var);
NSString *key = [NSStringstringWithUTF8String:keyName];
id value = [aDecoderdecodeObjectForKey:key];
[selfsetValue:value forKey:key];
}
free(ivar);
}
returnself;
}
@end
@implementation TURLSessionProtocol
+ (void)initialize
{
if (self == [TURLSessionProtocolclass])
{
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
TURLSessionFilterUrlPreObject = [[NSObjectalloc] init];
});
[selfsetFilterUrlPres:[NSSetsetWithObject:@"http://localhost:8081"]];
}
}
- (NSURLSession *)session {
if (!_session) {
_session = [NSURLSessionsessionWithConfiguration:[NSURLSessionConfigurationdefaultSessionConfiguration] delegate:selfdelegateQueue:nil];
}
return_session;
}
#pragma mark - publicFunc
+ (NSSet *)filterUrlPres {
NSSet *set;
@synchronized(TURLSessionFilterUrlPreObject)
{
set = TURLSessionFilterUrlPre;
}
return set;
}
+ (void)setFilterUrlPres:(NSSet *)filterUrlPre {
@synchronized(TURLSessionFilterUrlPreObject)
{
TURLSessionFilterUrlPre = filterUrlPre;
}
}
#pragma mark - privateFunc
- (NSString *)p_filePathWithUrlString:(NSString *)urlString {
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask,YES) lastObject];
NSString *fileName = [urlString md5String];
return [cachesPath stringByAppendingPathComponent:fileName];
}
- (BOOL)p_isUseCahceWithCacheData:(TURLProtocolCacheData *)cacheData {
if (cacheData ==nil) {
returnNO;
}
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:cacheData.addDate];
return timeInterval < KCacheTime;
}
+ (BOOL)p_isFilterWithUrlString:(NSString *)urlString {
BOOL state =NO;
for (NSString *strin TURLSessionFilterUrlPre) {
if ([urlString hasPrefix:str]) {
state = YES;
break;
}
}
return state;
}
#pragma mark - override
+(BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([request valueForHTTPHeaderField:KProtocolHttpHeadKey] ==nil && ![self p_isFilterWithUrlString:request.URL.absoluteString]) {
//拦截请求头中包含KProtocolHttpHeadKey的请求
// NSLog(@"request url:%@",request.URL.absoluteString);
returnYES;
}
returnNO;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading {
NSString *url = self.request.URL.absoluteString;//请求的链接
TURLProtocolCacheData *cacheData = [NSKeyedUnarchiver unarchiveObjectWithFile:[self p_filePathWithUrlString:url]];
AppDelegate* app=(AppDelegate*)[[UIApplication sharedApplication] delegate];
//判断当前网络环境,有网的时候直接走服务器请求不用本地缓存
if (app.currentNetWorkState==AFNetworkReachabilityStatusReachableViaWWAN||app.currentNetWorkState==AFNetworkReachabilityStatusReachableViaWiFi) {
NSMutableURLRequest *request = [self.request mutableCopyWorkaround];
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.request.URL.absoluteString]];
[request setValue:@"test" forHTTPHeaderField:KProtocolHttpHeadKey];
self.downloadTask = [self.session dataTaskWithRequest:request];
[self.downloadTask resume];
return;
}
if ([self p_isUseCahceWithCacheData:cacheData]) {
//有缓存并且缓存没过期
if (cacheData.redirectRequest) {
[self.client URLProtocol:self wasRedirectedToRequest:cacheData.redirectRequest redirectResponse:cacheData.response];
} else if (cacheData.response){
[self.client URLProtocol:self didReceiveResponse:cacheData.response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:cacheData.data];
[self.client URLProtocolDidFinishLoading:self];
}
} else {
NSMutableURLRequest *request = [self.request mutableCopyWorkaround];
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.request.URL.absoluteString]];
[request setValue:@"test" forHTTPHeaderField:KProtocolHttpHeadKey];
self.downloadTask = [self.session dataTaskWithRequest:request];
[self.downloadTask resume];
}
}
- (void)stopLoading {
[self.downloadTask cancel];
self.cacheData =nil;
self.downloadTask =nil;
self.response =nil;
}
#pragma mark - session delegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
//处理重定向问题
if (response !=nil) {
NSMutableURLRequest *redirectableRequest = [request mutableCopyWorkaround];
TURLProtocolCacheData *cacheData = [[TURLProtocolCacheData alloc] init];
cacheData.data = self.cacheData;
cacheData.response = response;
cacheData.redirectRequest = redirectableRequest;
[NSKeyedArchiver archiveRootObject:cacheData toFile:[self p_filePathWithUrlString:request.URL.absoluteString]];
[self.client URLProtocol:self wasRedirectedToRequest:redirectableRequest redirectResponse:response];
completionHandler(request);
} else {
completionHandler(request);
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
// 允许处理服务器的响应,才会继续接收服务器返回的数据
completionHandler(NSURLSessionResponseAllow);
self.cacheData = [NSMutableData data];
self.response = response;
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//下载过程中
[self.client URLProtocol:self didLoadData:data];
[self.cacheData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// 下载完成之后的处理
if (error) {
NSLog(@"error url = %@",task.currentRequest.URL.absoluteString);
[self.client URLProtocol:self didFailWithError:error];
} else {
//将数据的缓存归档存入到本地文件中
NSLog(@"ok url = %@",task.currentRequest.URL.absoluteString);
TURLProtocolCacheData *cacheData = [[TURLProtocolCacheData alloc] init];
cacheData.data = [self.cacheData copy];
cacheData.addDate = [NSDate date];
cacheData.response = self.response;
[NSKeyedArchiver archiveRootObject:cacheData toFile:[self p_filePathWithUrlString:self.request.URL.absoluteString]];
[self.clientURLProtocolDidFinishLoading:self];
}
}
@end