使用NSURLProtocol实现离线缓存

一、说明:

NSURLProtocol可以拦截任何网络请求,包含UIWebView中发出的所有请求。但是在WKWebView中,只能拦截到最初始的请求,内嵌的资源下载拦截不到。比如通过WKWebView加载"http://www.baidu.com",则只能拦截到"http://www.baidu.com",网页内部的资源加载拦截不到。页面跳转属于最初始请求之内,可以拦截到。


二、创建NSURLProtocol的子类,通过下面的代码注册此协议类:

[NSURLProtocol registerClass:[MyURLProtocol class]];


三、下面是此子类的代码:


#import "MyURLProtocol.h"


#define MyURLProtocolHandled @"MyURLProtocolHandled"


//创建archive数据模型,重写编码解码协议

@interface MyCacheData : NSObject


@property(nonatomic,strong) NSURLRequest *request;

@property(nonatomic,strong) NSURLResponse *response;

@property(nonatomic,strong) NSData *data;


@end


@interface NSURLRequest (MutableCopyWorkaround)


- (id)mutableCopyWorkaround;


@end


@interface MyURLProtocol ()


@property(nonatomic,strong) NSURLConnection *connection;

@property(nonatomic,strong) NSMutableData *httpData;

@property(nonatomic,strong) NSURLResponse *response;


@end


@implementation MyURLProtocol


#pragma mark - 重写NSURLProtocol子类方法


+ (BOOL)canInitWithRequest:(NSURLRequest *)request

{

    //如果此请求是拦截到请求之后,接管请求而发起的新请求,则不处理。

    if ([request.URL.scheme isEqualToString:@"http"] &&

        [request valueForHTTPHeaderField:MyURLProtocolHandled] == nil)

    {

        return YES;

    }

    return NO;

}


+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

{

    return request;

}


+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a

                       toRequest:(NSURLRequest *)b

{

    return [super requestIsCacheEquivalent:a toRequest:b];

}


- (void)startLoading

{

    //如果发现已经存在此请求的缓存数据,则返回缓存数据,否则发起新的请求从服务求加载数据

    MyCacheData *cacheData = [NSKeyedUnarchiver unarchiveObjectWithFile:

                              [self cachePathForRequest:self.request]];

    if(cacheData != nil)

    {

        NSData *data = cacheData.data;

        NSURLResponse *response = cacheData.response;

        NSURLRequest *redirectRequest = cacheData.request;

        

        //使用NSURLProtocolClient做请求转向,直接将请求和数据转发到之前的请求

        if(redirectRequest != nil)

        {

            [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest

                      redirectResponse:response];

        }

        else

        {

            [[self client] URLProtocol:self didReceiveResponse:response

                    cacheStoragePolicy:NSURLCacheStorageNotAllowed];

            [[self client] URLProtocol:self didLoadData:data];

            [[self client] URLProtocolDidFinishLoading:self];

        }

    }

    else

    {

//接管此网络请求,发起一个新的请求,后续会将新请求拿到的数据交给之前的旧请求

        NSMutableURLRequest *connectionRequest = [[self request] mutableCopyWorkaround];

        //增加标记,标示是由我们接管而发出的请求

        [connectionRequest setValue:@"Tag" forHTTPHeaderField:MyURLProtocolHandled];

        self.connection = [NSURLConnection connectionWithRequest:connectionRequest

                                                        delegate:self];

    }

}


- (void)stopLoading

{

    [self.connection cancel];

    self.connection = nil;

}


#pragma mark - 网络请求代理


- (NSURLRequest *)connection:(NSURLConnection *)connection

             willSendRequest:(NSURLRequest *)request

            redirectResponse:(NSURLResponse *)response

{

    if(response != nil)

    {

        NSMutableURLRequest *redirectableRequest = [request mutableCopyWorkaround];

        

        //缓存数据

        MyCacheData *cacheData = [MyCacheData new];

        [cacheData setData:self.httpData];

        [cacheData setResponse:response];

        [cacheData setRequest:redirectableRequest];

        

        [NSKeyedArchiver archiveRootObject:cacheData

                                    toFile:[self cachePathForRequest:[self request]]];

        

        //将请求和缓存的响应数据转向到之前的请求

        [[self client] URLProtocol:self wasRedirectedToRequest:redirectableRequest

                  redirectResponse:response];

        return redirectableRequest ;

    }

    return request;

}


- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection

{

    return YES;

}


- (void)connection:(NSURLConnection *)connection

didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

{

    [self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];

}


- (void)connection:(NSURLConnection *)connection

didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

{

    [self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];

}


- (void)connection:(NSURLConnection *)connection

didReceiveResponse:(NSURLResponse *)response

{

    //保存响应对象

    self.response = response;

    [self.client URLProtocol:self didReceiveResponse:response

          cacheStoragePolicy:NSURLCacheStorageNotAllowed];

}


- (void)connection:(NSURLConnection *)connection

    didReceiveData:(NSData *)data

{

    [self.client URLProtocol:self didLoadData:data];

    

    //保存服务器返回的数据

    if(self.httpData == nil) {

        self.httpData = [NSMutableData dataWithData: data];

    }

    else

    {

        [self.httpData appendData:data];

    }

}


- (NSCachedURLResponse *)connection:(NSURLConnection *)connection

                  willCacheResponse:(NSCachedURLResponse *)cachedResponse

{

    return cachedResponse;

}


- (void)connectionDidFinishLoading:(NSURLConnection *)connection

{

    [self.client URLProtocolDidFinishLoading:self];

    

    //请求加载完毕之后,将数据缓存

    MyCacheData *cacheData = [MyCacheData new];

    [cacheData setData:self.httpData];

    [cacheData setResponse:self.response];

    [NSKeyedArchiver archiveRootObject:cacheData

                                toFile:[self cachePathForRequest:self.request]];

    

    self.connection = nil;

    self.httpData = nil;

    self.response = nil;

}


- (void)connection:(NSURLConnection *)connection

  didFailWithError:(NSError *)error

{

    [self.client URLProtocol:self didFailWithError:error];

    

    self.connection = nil;

    self.httpData = nil;

    self.response = nil;

}


#pragma mark - 为请求创建缓存路径


- (NSString *)cachePathForRequest:(NSURLRequest *)aRequest

{

    NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

                                                  NSUserDomainMask, YES) lastObject];

    return [cachesPath stringByAppendingPathComponent:

            [NSString stringWithFormat:@"%ld", [[[aRequest URL] absoluteString] hash]]];

}


@end


@implementation NSURLRequest (MutableCopyWorkaround)


- (id) mutableCopyWorkaround {

    NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequest alloc]

                                              initWithURL:[self URL]

                                              cachePolicy:[self cachePolicy]

                                              timeoutInterval:[self timeoutInterval]];

    [mutableURLRequest setAllHTTPHeaderFields:[self allHTTPHeaderFields]];

    return mutableURLRequest;

}


@end


@implementation MyCacheData


-(id) initWithCoder:(NSCoder *) aDecoder

{

    self = [super init];

    if(!self) {

        return nil;

    }

    [self setData:[aDecoder decodeObjectForKey:@"data"]];

    [self setRequest:[aDecoder decodeObjectForKey:@"request"]];

    [self setResponse:[aDecoder decodeObjectForKey:@"response"]];

    

    return self;

}


- (void)encodeWithCoder:(NSCoder *)aCoder

{

    [aCoder encodeObject:[self data] forKey:@"data"];

    [aCoder encodeObject:[self request] forKey:@"request"];

    [aCoder encodeObject:[self response] forKey:@"response"];

}


@end


部分代码转自:http://tanlimin201.blog.163.com/blog/static/38171407201383032914736/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500067.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WKWebView概述

一、概述1.iOS 8 SDK中发布了新的WebView框架----WebKit.framework。2.WebKit使用WKWebView来代替IOS的UIWebView和OSX的NSWebView,并且使用Nitro JavaScript引擎,这意味着所有第三方浏览器运行JavaScript将会跟safari一样快。3.内存问题:(1)…

CoreData 自定义数据类型

在CoreData中,Entity中Attribute的类型只有固定的几种可选。如下图: 如果我们要想直接存放UIImage到数据库,如何做? 1.在coredata中新建的attribute中类形选择Transformable. 意思表示这个字段是自定义的类型。 2.在生成的NSMana…

XMLDictionary iOS的XML处理包

1.概述:XMLDictionary 提供一种简单的方法实现 iOS 和 Mac OS X 下解析和生成 XML 的方法。XMLDictionary 将 XML 转成 NSDictionary ,也可以将 NSDictionary 装成 XML 结构的字符串。2.实现原理:XMLDictionary 使用 NSXMLParser 类解析XML,使…

CoreData并发操作模式简介

iOS5.0中,苹果为CoreData的并发处理添加了两个内容。一、首先介绍第一个内容:CoreData框架中的NSManagedObjectContext类增加新的初始化方法:initWithConcurrencyType:(NSManagedObjectContextConcurrencyType)ct;1.参数方法介绍:…

java图片的导出,并压缩

java图片的导出,并压缩 java 压缩包jar包使用的是commons-compress-1.6.jar /*** 导出图片* param request* param response*/RequestMapping("/exportPicture")public void exportPicture(HttpServletRequest request,HttpServletResponse response) throws Exce…

【Linux系统基础】(3)在Linux上部署运维监控Zabbix和Grafana

目录 运维监控Zabbix部署简介安装安装前准备 - Mysql安装Zabbix Server 和 Zabbix Agenta. 安装Zabbix yum库b. 安装Zabbix Server、前端、Agentc. 初始化Mysql数据库d. 为Zabbix Server配置数据库e. 配置Zabbix的PHP前端 配置zabbix 前端(WEB UI) 运维监…

Pods简介

每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如 Java 语言的 Maven,nodejs 的 npm。随着 iOS 开发者的增多,业界也出现了为 iOS 程序提供依赖管理的工具,它的名字叫做:CocoaPods。 CocoaPods项目…

HashMap源码解释

HashMap 前言: 本文的hashMap是基于jdk1.7的hashMap. 关于jdk1.8的hashMap在另一篇中,那里将会介绍与1.7的差异与优势 首先基础知识介绍: 1.HashMap的成员变量   int DEFAULT_INITIAL_CAPACITY 16&#xff1a;默认的初始容量为2 ^ 4   int MAXIMUM_CAPACITY 1 <<…

MagicRecord For IOS 简介

一、概述 MagicalRecord 灵感来自于简洁的Ruby语言中 Rails Active Record 查询方式. MagicalRecord 这个开源库的核心思想是: 1.清除 Core Data 相关的代码2.简洁的清除,简单的一行搜索记录的功能3.当然允许使用NSFetchRequest,当存在着复杂的搜索条件时 二、使用 1. 导入框架…

对象引用 String引用 基本类型引用 差别

最近遇到一个线上问题,原因是忽略的引用的一些语法,导致出错,现在记录一下: Testpublic void testList(){List<String> list new ArrayList<String>();list.add("1");list.add("2");list.add("3");List<String> list2 new …

Mantle For iOS

Mantle可以很方便的去书写一个模型层的代码。 使用它可以很方便的去反序列化JSON或者序列化为JSON(需要在MTLModel子类中实现<MTLJSONSerializing>协议) 使用一个解释器MTLJSONAdapter去转换模型对象。 NSError *error nil; MyObject *myObject [MTLJSONAdapter modelO…

String的split方法的使用

1.引言 split方法很常用,记得我入职公司的时候,第一道笔试题就是关于字符串的拆分拼装等,这也足以说明,大公司对这些方法的重视程度. 其实我们平时一般用到的都是这些方法最简单的用法,但如果你不了解他的实现原理,碰到某些特殊问题终究是会被卡住的,于是就产生了所谓的bug,而…

ReactiveCocoa入门

概述 为什么要使用RAC&#xff1f;一个怪怪的东西&#xff0c;从Demo看也没有让代码变得更好、更短&#xff0c;相反还造成理解上的困难&#xff0c;真的有必要去学它么&#xff1f;相信这是大多数人在接触RAC时的想法。RAC不是单一功能的模块&#xff0c;它是一个Framework&am…

[前台]---input标签中的hidden,浏览器差异问题

前言: 这是一个比较简单的问题,也有人犯过这样的错误,如果你是一个人在编码,并且没有专门的去测试浏览器差异,这个或许会坑到你 问题描述: 用input标签的时候,需要把这个input隐藏掉,于是先这样做: <input hidden id"xxx" value"xxx"/> 这行代码…

C++和Objective-C混编(官方文档翻译)

苹果的Objective-C编译器允许用户在同一个源文件里自由地混合使用C和Objective-C&#xff0c;混编后的语言叫Objective-C。有了它&#xff0c;你就可以在Objective-C应用程序中使用已有的C类库。 Build Setting中要设定编译文件类型设置&#xff0c;如下图&#xff1a;Objectiv…

SpringMVC获取response的问题

SpringMVC获取response的问题: 关于用以下这种方式获取response的一些问题: ((ServletWebRequest) RequestContextHolder.getRequestAttributes()).getResponse(); 网上对于这种方式获取response的描述很多,我用的是jar包版本是3.2.9.release web.xml中肯定也是加了Request…

BigDecimal的个人总结

前言: 互联网公司,对于BigDecimal的使用,还是较为频繁的,那么就会涉及到关于这个类型的种种问题. 1:为什么使用BigDecimal 首先java八大基本类型真的很基本,4个整型搞不了小数,double和float搞的了小数,但搞不好,关键时刻就调链子,当然这也和他们存储方式有关(二进制无法精确的…

MagicRecord For IOS API深层解析

一、NSManagedObjectContext创建和获取1.默认上下文(每种只存在一个)&#xff08;1&#xff09;MR_rootSavingContext&#xff1a;此方法返回的上下文类型为NSPrivateQueueConcurrencyType(后台线程)&#xff0c;直接关联持久化协调器&#xff0c;对此上下文对象执行保存方法&a…

BeanUtils.copyProperties(对象A,对象B)

对于两个实例的复制,如果属性名字相同,则可以通过这个方法来操作,但是在使用的时候,一定要注意BeanUtils用的是哪个包的,因为常用的两个包,都有这个类和对应的方法,而复制方向却相反,所以一定要注意自动导包时选择的包!!!!!! 1.import org.springframework.beans.BeanUtils 这…

Xcode中的-ObjC和-all_load

有时候使用第三方库的时候&#xff0c;需要在Xcode的Build Settings下Other Linker Flags里面加入-ObjC标志。 之所以使用该标志&#xff0c;和Objective-C的一个重要特性&#xff1a;类别&#xff08;category)有关。根据这里的解释&#xff0c;Unix的标准静态库实现和Object…