博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IOS 使用NSURLProtocol 拦截网络请求实现缓存
阅读量:2346 次
发布时间:2019-05-10

本文共 10733 字,大约阅读时间需要 35 分钟。

最近项目需要实现一个WKWebview的缓存功能,然后想到通过拦截http/https请求,然后通过url字符串的MD5串来作为“Key” 存储和读取缓存,缓存数据使用YYCache 这个缓存框架还是很不错的,有通过链表实现的内存缓存,和数据库以及文件实现的磁盘缓存,这个就不多说了,具体可以github 上看源码,今天主要讲通过NSURLProtocol来实现拦截Http/https 中间可能牵涉到一些其他文件,在Demo工程里细说

主要的类NSURLProtocol 是苹果为我们提供的 URL Loading System 的一部分,这是一张从官方文档贴过来的图片:
在这里插入图片描述

在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。

这样,我们需要解决的核心问题就变成了如何使用 NSURLProtocol 来处理所有的网络请求,这里使用苹果官方文档中的 SCYCacheURLProtocol 进行介绍,你可以点击这里下载源代码。

决定是否由当前协议来处理请求

+ (BOOL)canInitWithRequest:(NSURLRequest *)request;

发送请求,可以在这里处理各种请求

- (void)startLoading

加载本地数据然后返回给网络请求的response,并且结束当前网络请求

- (void)loadProJectData:(NSData *)data{
NSURLResponse* response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:@"application/octet-stream" expectedContentLength:data.length textEncodingName:nil]; [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; [[self client] URLProtocol:self didLoadData:data]; [[self client] URLProtocolDidFinishLoading:self]; // NSLog(@"直接使用缓存.............缓存的url == %@ ", self.request.URL.absoluteString); [[EzwWebToolUtils sharedInstance]addReuseCount];}

当然了如果本地没有缓存数据要向服务器获取这个就直接走正常网络请求

/// 请求最新- (void)loadRequest{
NSMutableURLRequest *connectionRequest = [[self request] mutableCopyWorkaround]; [connectionRequest setValue:@"" forHTTPHeaderField:SCYCachingURLHeader]; NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest delegate:self]; [self setConnection:connection];}

在这个工程中 SCYCacheURLProtocol.m 是需要重点关注的文件,SCYCacheURLProtocol 就是 NSURLProtocol 的子类:

#import 
@interface SCYCacheURLProtocol : NSURLProtocol@end
////  SCYCacheURLProtocol.m//  ProvidentFund////  Created by 9188 on 2016/12/12.//  Copyright © 2016年 9188. All rights reserved.//#import "SCYCacheURLProtocol.h"#import "Reachability.h"#import "NSString+CDEncryption.h"#import "SCYWebViewCacheModel.h"#import "NSURLRequest+MutableCopyWorkaround.h"#import "SCYLoanHTMLCache.h"#import "EzwWebToolUtils.h"#import "EzwSourceDataManager.h"static NSString *SCYCachingURLHeader = @"SCYCacheURLProtocolCache";static NSSet *SCYCachingSupportedSchemes;static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";static NSString * const CacheUrlStringKey = @"cacheUrlStringKey"; // 本地保存缓存urlKey的数组key@interface SCYCacheURLProtocol ()
@property (nonatomic, strong) NSURLConnection *connection;@property (nonatomic, strong) NSMutableData *data;@property (nonatomic, strong) NSURLResponse *response;@property (nonatomic, strong) SCYWebViewCacheModel *cacheModel;@end@implementation SCYCacheURLProtocol- (SCYWebViewCacheModel *)cacheModel{
if (!_cacheModel) {
_cacheModel = [[SCYWebViewCacheModel alloc] init]; } return _cacheModel;}+ (void)initialize{
if (self == [SCYCacheURLProtocol class]){
SCYCachingSupportedSchemes = [NSSet setWithObjects:@"http", @"https", nil]; }}//决定请求是否需要当前协议处理+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
//如果无法获取cdn 字符串 if([[EzwSourceDataManager sharedInstance]getCDNUrlStr].length==0){
return NO; } //scheme url 字符串前一部分 http 或者 https// 整个url 字符串构成scheme:resourceSpecifier if ([SCYCachingSupportedSchemes containsObject:[[request URL] scheme]] && ([request valueForHTTPHeaderField:SCYCachingURLHeader] == nil)){
//看看是否已经处理过了,防止无限循环 if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO; } return YES; } return NO;}//请求经过 + canInitWithRequest: 方法过滤之后,我们得到了所有要处理的请求,接下来需要对请求进行一定的操作+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
NSMutableURLRequest *mutableReqeust = [request mutableCopy]; return mutableReqeust;}/// 开始加载时自动调用--调用 super 的指定构造器方法,实例化一个对象,然后就进入了发送网络请求,获取数据并返回的阶段了- (void)startLoading{
//打标签,防止无限循环 [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:[[self request] mutableCopy]]; //加载ipa 包里数据 [[EzwWebToolUtils sharedInstance]addRequestCount]; NSData *datas = [[EzwSourceDataManager sharedInstance]getDatasFromPathStr:[[self.request URL] absoluteString]]; if (datas!=NULL) {
[self loadProJectData:datas]; return; } // 加载本地 SCYWebViewCacheModel *cacheModel = (SCYWebViewCacheModel *)[[SCYLoanHTMLCache defaultcache] objectForKey:[[[self.request URL] absoluteString] cd_md5HexDigest]]; if ([self useCache] && cacheModel == nil) {
// 可到达(有网)而且无缓存 才重新获取 [self loadRequest]; } else if(cacheModel) {
// 有缓存 [self loadCacheData:cacheModel]; } else {
// 没网 没缓存 NSLog(@"没网也没缓存....."); }// NSLog(@"%@",[[self.request URL] absoluteString] );}- (void)loadProJectData:(NSData *)data{
NSURLResponse* response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:@"application/octet-stream" expectedContentLength:data.length textEncodingName:nil]; [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; [[self client] URLProtocol:self didLoadData:data]; [[self client] URLProtocolDidFinishLoading:self]; // NSLog(@"直接使用缓存.............缓存的url == %@ ", self.request.URL.absoluteString); [[EzwWebToolUtils sharedInstance]addReuseCount];}- (void)stopLoading{
[[self connection] cancel];}#pragma mark - NSURLConnectionDelegate- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response{
if (response != nil) {
NSMutableURLRequest *redirectableRequest = [request mutableCopyWorkaround]; [redirectableRequest setValue:nil forHTTPHeaderField:SCYCachingURLHeader]; [self cacheDataWithResponse:response redirectRequest:redirectableRequest]; [[self client] URLProtocol:self wasRedirectedToRequest:redirectableRequest redirectResponse:response]; return redirectableRequest; } else {
return request; }}- (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];}- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
[self setResponse:response]; [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // We cache ourselves.}- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
[[self client] URLProtocolDidFinishLoading:self]; /// 自己项目设置的逻辑 即是服务器版本号 > 本地版本号 则需要刷新 /// 先移除之前的缓存,在缓存新的。。对吧 这里逻辑看情况而定// if ([SCYLoanUrlType() isEqualToString:@"1"] && SCYLoanSerViceVersion().integerValue > SCYLoanLocalVersion().integerValue) { // 缓存最新的时候 移除之前 loacalVersion localUrl// [[SCYLoanHTMLCache defaultcache] removeAllObjects];// [[NSUserDefaults standardUserDefaults] removeObjectForKey:CacheUrlStringKey];// NSLog(@"刷新网页成功.........");// } 有缓存则不缓存 SCYWebViewCacheModel *cacheModel = (SCYWebViewCacheModel *)[[SCYLoanHTMLCache defaultcache] objectForKey:[[[self.request URL] absoluteString] cd_md5HexDigest]]; if (!cacheModel) {
[self cacheDataWithResponse:self.response redirectRequest:nil]; } }#pragma mark - private/** * 存储缓存数据 * @param response response * @param redirectableRequest 重定向request */- (void)cacheDataWithResponse:(NSURLResponse *)response redirectRequest:(NSMutableURLRequest *)redirectableRequest{
[self.cacheModel setResponse:response]; [self.cacheModel setData:[self data]]; [self.cacheModel setRedirectRequest:redirectableRequest]; NSString *cacheStringkey = [[[self.request URL] absoluteString] cd_md5HexDigest]; //这个方法NSArchive 存储数据 [[SCYLoanHTMLCache defaultcache] setObject:self.cacheModel forKey:cacheStringkey withBlock:^{
// 注意 这里加载.css jpg 等资源路径的时候,这个类已经更新了(即数组加urlkey数组的时候,不能在当前类一直加,而是先从本地取了之后再加)// NSMutableArray *array = [[[NSUserDefaults standardUserDefaults] objectForKey:CacheUrlStringKey] mutableCopy];// if (!array) { array = @[].mutableCopy; }//// [array addObject:cacheStringkey];// //设置缓存// [[NSUserDefaults standardUserDefaults] setObject:array forKey:CacheUrlStringKey];// NSLog(@".....重置了缓存 key == CacheUrlStringKey....");// NSLog(@"%@",self.request.URL);// NSLog(@".....新增了缓存key %@ ...., 当前缓存个数%ld",cacheStringkey, array.count); // [[NSUserDefaults standardUserDefaults]synchronize]; }];}/// 请求最新- (void)loadRequest{
NSMutableURLRequest *connectionRequest = [[self request] mutableCopyWorkaround]; [connectionRequest setValue:@"" forHTTPHeaderField:SCYCachingURLHeader]; NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest delegate:self]; [self setConnection:connection];}- (BOOL)useCache{
BOOL reachable = (BOOL) [[Reachability reachabilityWithHostName:[[[self request] URL] host]] currentReachabilityStatus] != NotReachable; //NSLog(@"网络是否可到达 1可到达 0不可到达............. %d", reachable); return reachable;}- (void)appendData:(NSData *)newData{
if ([self data] == nil) {
[self setData:[newData mutableCopy]]; } else {
[[self data] appendData:newData]; }}- (void)loadCacheData:(SCYWebViewCacheModel *)cacheModel{
if (cacheModel) {
NSData *data = [cacheModel data]; NSURLResponse *response = [cacheModel response]; NSURLRequest *redirectRequest = [cacheModel redirectRequest]; if (redirectRequest) {
[[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response]; // NSLog(@"redirectRequest............. 重定向"); } else {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [[self client] URLProtocol:self didLoadData:data]; [[self client] URLProtocolDidFinishLoading:self]; // NSLog(@"直接使用缓存.............缓存的url == %@ ", self.request.URL.absoluteString); [[EzwWebToolUtils sharedInstance]addReuseCount]; } } else {
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]]; }}@end

转载地址:http://ghnvb.baihongyu.com/

你可能感兴趣的文章
以下关于STL的描述中,错误的有
查看>>
假设某棵二叉查找树的所有键均为1到10的整数,现在我们要查找5。下面____不可能是键的检查序列。
查看>>
给定一个整数sum,从有N个无序元素的数组中寻找元素a、b、c、d,使得 a+b+c+d =sum,最快的平均时间复杂度是____。
查看>>
设二叉树结点的先根序列、中根序列和后根序列中,所有叶子结点的先后顺序____。
查看>>
将整数序列(7-2-4-6-3-1-5)按所示顺序构建一棵二叉排序树a(亦称二叉搜索树),之后将整数8按照二叉排序树规则插入树a中,请问插入之后的树a中序遍历结果是____。
查看>>
IP地址、子网掩码、网络号、主机号、网络地址、主机地址
查看>>
已知int a[]={1,2,3,4,5};int*p[]={a,a+1,a+2,a+3};int **q=p;表达式*(p[0]+1)+**(q+2)的值是____。
查看>>
CPU输出数据的速度远远高于打印机的打印速度,为了解决这一矛盾,可采用()
查看>>
整型字符常量和字符字面量的区别 sizeof(char) 和 sizeof('a')
查看>>
表的主键特点中,说法不正确的是()
查看>>
用变量a给出下面的定义:一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整型数
查看>>
冯诺依曼工作方式的基本特点是____
查看>>
下列关于文件索引结构的叙述中,哪些是正确的?
查看>>
Java异常处理
查看>>
JQueryUI实现对话框
查看>>
Java流(Stream)/文件(File)/IO
查看>>
文件处理(压缩与解压)
查看>>
Java中的目录
查看>>
JQuery实现对select选择框的赋值
查看>>
SweetAlert插件
查看>>