因为公司一个以H5为主要核心的项目,进行了了大量的调研寻找合适的iOS网页离线缓存查看,最后敲定使用基于NSURLCache框架开发这个功能。项目完毕,因为在网上没有找到响应需求的良好开源库,在网上找到一个基于NSURLCache开发的项目,因为并不完整,就利用休整时间借鉴原项目的经验开发了一个实现网页离线浏览的开源库(DPWebViewLocalCache)。
一、上图片,看效果
二、上代码,Demo代码实现
实现代码:
//
// DPLocalCache.h
// DPLocalCache
//
// Created by yupeng xia on 2016/11/10.
// Copyright © 2016年 yupeng xia. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Reachability.h"
#import "DPLocalCacheTool.h"
typedef enum : NSUInteger{
NORMAL_MODE = 0, //系统的缓存 (ustomCache和NSURLCache的功能是一样的)
DOWNLOAD_MODE = 1 //自定义缓存 (CustomURLCache则可以实现包含自定义下载目录,设置过期时间的子功能的下载功能)
}MODE_TYPE;
@interface DPLocalCache : NSURLCache
@property(nonatomic,assign)NSInteger cacheTime; //添加缓存间隔时间
@property(nonatomic,strong)NSString *diskPath; //沙盒路径
@property(nonatomic,strong)NSString *subDirectory; //子路径
@property(nonatomic,assign)MODE_TYPE aMode; //缓存类型
@property(nonatomic,strong)NSMutableDictionary *responseDictionary; //响应的字典
@property(nonatomic,strong)NSMutableDictionary *localReourcePath; //手动添加本地缓存
@property(nonatomic,strong)NSArray *keyArray; //URL需要删除的键值对(某些键值对会改变,因此需要删除)
///初始缓存存储本地空间
- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path cacheTime:(NSInteger)cacheTime modeTybe:(MODE_TYPE)aModeTybe subDirectory:(NSString*)subDirectory;
///删除缓存文件夹
- (void)deleteCacheFolder;
@end
//
// DPLocalCache.m
// DPLocalCache
//
// Created by yupeng xia on 2016/11/10.
// Copyright © 2016年 yupeng xia. All rights reserved.
//
#import "DPLocalCache.h"
@implementation DPLocalCache
- (id)initWithMemoryCapacity:(NSUInteger)aMemoryCapacity diskCapacity:(NSUInteger)aDiskCapacity diskPath:(NSString *)aDiskPath cacheTime:(NSInteger)aCacheTime modeTybe:(MODE_TYPE)aModeTybe subDirectory:(NSString*)aSubDirectory{
if (self = [selfinitWithMemoryCapacity:aMemoryCapacitydiskCapacity:aDiskCapacitydiskPath:aDiskPath]) {
if (aDiskPath){
self.diskPath = aDiskPath;
}else{
self.diskPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES)lastObject];
}
NSLog(@"Local cache disk path: %@",self.diskPath);
self.cacheTime = aCacheTime;
self.aMode = aModeTybe;
self.subDirectory = aSubDirectory;
self.responseDictionary = [NSMutableDictionarydictionaryWithCapacity:0];
//手动添加本地缓存
self.localReourcePath = [NSMutableDictionarydictionaryWithCapacity:0];
}
return self;
}
#pragma mark <----------重写NSURLCache函数---------->
#pragma mark 缓存的响应请求 (如果对应的NSURLRequest没有cached的response那么返回nil)
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request{
NSString *url = request.URL.absoluteString;
// NSLog(@"normal mode:%@",[[request URL]absoluteString]);
// NSLog(@"data from request %@",[request description]);
if (_aMode ==NORMAL_MODE) {//使用系统缓存类型
return [supercachedResponseForRequest:request];
}
if ([request.HTTPMethodcompare:@"GET"] !=NSOrderedSame) {//不处理非get请求
return [supercachedResponseForRequest:request];
}
if ([self.localReourcePathobjectForKeyedSubscript:url] !=nil) {//手动添加本地缓存不为空
NSCachedURLResponse *cachedResponse = [selfloadLocalResouce:requestpath:[self.localReourcePathobjectForKey:url]];
if (cachedResponse !=nil) {//添加成功
return cachedResponse;
}else {//添加失败
return [selfdataFromRequest:request];
}
}
return [selfdataFromRequest:request];
}
/*
*网络请求本地缓存处理
*没有网络使用本地缓存;
*有网络使添加本地缓存;
*/
- (NSCachedURLResponse *)dataFromRequest:(NSURLRequest *)request{
NSString *url = request.URL.absoluteString;
//sessionId(不断改变,加载本地缓存,需要删除当前字段)
for (NSString *keyStrin_keyArray) {
NSRange range;
range = [url rangeOfString:keyStr];
if (range.location !=NSNotFound) {
//用户为登陆,去除已经拼接的sessionId
url = [NSMutableStringstringWithFormat:@"%@",[DPLocalCacheToolurlDeleteValueOfParam:urlwithParam:keyStr]];
}
}
//当前资源的本地存储子目录的文件名
NSString *fileName = [selfcacheRequestFileName:url];
//当前资源描述文件的本地存储子目录的文件名
NSString *otherInfoFileName = [selfcacheRequestDescriptionFileName:url];
//当前资源的本地存储路径
NSString *filePath = [selfcacheFilePath:fileName];
//当前资源描述文件的本地存储路径
NSString *otherInfoPath = [selfcacheFilePath:otherInfoFileName];
NSDate *date = [NSDatedate];
NSFileManager *fileManager = [NSFileManagerdefaultManager];
//获取网络状态
Reachability *reachability = [ReachabilityreachabilityForInternetConnection];
NetworkStatus internetStatus = [reachabilitycurrentReachabilityStatus];
if (internetStatus ==NotReachable) {//网络不可用,使用本地缓存
if ([fileManagerfileExistsAtPath:filePath]) {
NSDictionary *otherInfo = [NSDictionarydictionaryWithContentsOfFile:otherInfoPath];
NSData *data = [NSDatadataWithContentsOfFile:filePath];
NSURLResponse *response = [[NSURLResponsealloc]initWithURL:request.URL
MIMEType:[otherInfo objectForKey:@"MIMEType"]
expectedContentLength:data.length
textEncodingName:[otherInfoobjectForKey:@"textEncodingName"]];
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponsealloc]initWithResponse:responsedata:data];
return cachedResponse;
}
}else{//网络可用,相对应的网页本地缓存不存在,存储本地缓存
BOOL expire = false;
NSDictionary *otherInfo = [NSDictionarydictionaryWithContentsOfFile:otherInfoPath];
if (self.cacheTime >0) {
NSInteger createTime = [[otherInfoobjectForKey:@"time"]intValue];
if(createTime){
if (createTime +self.cacheTime > [datetimeIntervalSince1970]) {
expire = true;
}
}
}
if (expire == false) {//缓存到期,重新下载缓存数据
id boolExsite = [_responseDictionaryobjectForKey:url];
if (boolExsite ==nil) {
[self.responseDictionarysetValue:[NSNumbernumberWithBool:TRUE]forKey:url];
__blockNSCachedURLResponse *cachedResponse =nil;
[NSURLConnectionsendAsynchronousRequest:requestqueue:[[NSOperationQueuealloc]init]completionHandler:^(NSURLResponse *response,NSData *data,NSError *error) {
//NSLog(@"下载资源结束返回地址: %@", request.URL.absoluteString);
if (response && date) {
if (error) {
NSLog(@"error : %@", error);
cachedResponse = nil;
}else {
//生成缓存资源的描述文件
NSDictionary *dict = [NSDictionarydictionaryWithObjectsAndKeys:[NSStringstringWithFormat:@"%f", [datetimeIntervalSince1970]], @"time", response.MIMEType,@"MIMEType", response.textEncodingName,@"textEncodingName",nil];
//缓存资源的描述文件存入本地
BOOL otherInfoResult = [dictwriteToFile:otherInfoPathatomically:YES];
//缓存资源存入本地
BOOL result = [datawriteToFile:filePathatomically:YES];
if(otherInfoResult ==NO || result ==NO) {
NSLog(@"写入错误,路径:%@",filePath);
}else {
NSLog(@"写入成功,路径:%@",filePath);
}
cachedResponse = [[NSCachedURLResponsealloc]initWithResponse:responsedata:data];
}
}
}];
[superstoreCachedResponse:cachedResponseforRequest:request];
return cachedResponse;
}
}
}
return nil;
}
//手动添加本地缓存处理,输出缓存内容
- (NSCachedURLResponse*)loadLocalResouce:(NSURLRequest*)request path:(NSString*)path{
//NSLog(@"load from local source %@,%@",request.URL.absoluteString,path);
NSFileManager* fileManager = [NSFileManagerdefaultManager];
if (![fileManager fileExistsAtPath:path]) {
return nil;
}
// Load the data
NSData *data = [NSDatadataWithContentsOfFile:path];
// Create the cacheable response
NSURLResponse *response = [[NSURLResponsealloc]
initWithURL:[requestURL]
MIMEType:[selfmimeTypeForPath:[[requestURL]absoluteString]]
expectedContentLength:[datalength]
textEncodingName:nil];
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponsealloc]initWithResponse:responsedata:data];
[self storeCachedResponse:cachedResponseforRequest:request];
return cachedResponse;
}
#pragma mark 移除特定NSURLRequest的cache
- (void)removeCachedResponseForRequest:(NSURLRequest *)request{
[super removeCachedResponseForRequest:request];
//这句要不要需要测试一下
NSString *url = request.URL.absoluteString;
NSString *fileName = [selfcacheRequestFileName:url];
NSString *otherInfoFileName = [selfcacheRequestDescriptionFileName:url];
NSString *filePath = [selfcacheFilePath:fileName];
NSString *otherInfoPath = [selfcacheFilePath:otherInfoFileName];
NSFileManager *fileManager = [NSFileManagerdefaultManager];
[fileManager removeItemAtPath:filePatherror:nil];
[fileManager removeItemAtPath:otherInfoPatherror:nil];
}
#pragma mark 移除所有的cache
- (void)removeAllCachedResponses{
[super removeAllCachedResponses];
}
#pragma mark <----------缓存使用过程中的处理---------->
#pragma mark 获取文件路径_file:文件名称
- (NSString *)cacheFilePath:(NSString *)file{
NSString *path = [NSStringstringWithFormat:@"%@/%@",self.diskPath, [selfcacheFolder]];
NSFileManager *fileManager = [NSFileManagerdefaultManager];
BOOL isDir;
if ([fileManager fileExistsAtPath:path isDirectory:&isDir] && isDir) {
}else {
[fileManager createDirectoryAtPath:pathwithIntermediateDirectories:YESattributes:nilerror:nil];
}
NSString *subDirPath = [NSStringstringWithFormat:@"%@/%@/%@",self.diskPath,[selfcacheFolder],self.subDirectory];
if ([fileManager fileExistsAtPath:subDirPath isDirectory:&isDir] && isDir) {
}else {
[fileManager createDirectoryAtPath:subDirPathwithIntermediateDirectories:YESattributes:nilerror:nil];
}
// NSLog(@"缓存本地存储路径: %@",[NSString stringWithFormat:@"%@/%@", subDirPath, file]);
return [NSStringstringWithFormat:@"%@/%@", subDirPath, file];
}
#pragma mark 删除缓存文件夹
- (void)deleteCacheFolder{
NSString *path = [NSStringstringWithFormat:@"%@/%@/%@",self.diskPath, [selfcacheFolder],_subDirectory];
NSLog(@"delete file:%@",path);
NSFileManager *fileManager = [NSFileManagerdefaultManager];
[fileManager removeItemAtPath:patherror:nil];
}
#pragma mark 根据当前资源的网址,生成当前资源的本地存储子目录的文件名
- (NSString *)cacheRequestFileName:(NSString *)requestUrl{
//子目录名字转义
NSString *subPath = [DPLocalCacheToolmd5Hash:[DPLocalCacheToolmd5Hash:requestUrl]];
//获得文件的后缀名(不带'.')
NSString *exestr = [requestUrlpathExtension];
if (exestr.length >0) {
//处理文件,根据相对应的格式生成相对应文件名称
subPath = [NSStringstringWithFormat:@"%@.%@",subPath,exestr];
}
// NSLog(@"资源_子目录:%@",subPath);
return subPath;
}
#pragma mark 根据当前资源的网址,生成当前资源描述文件的本地存储子目录的文件名
- (NSString *)cacheRequestDescriptionFileName:(NSString *)requestUrl{
NSString *subPath = [DPLocalCacheToolmd5Hash:[NSStringstringWithFormat:@"%@-otherInfo", requestUrl]];
// NSLog(@"资源_描述文件_子目录:%@",subPath);
return subPath;
}
#pragma mark 自定义缓存_本地存储文件夹
- (NSString *)cacheFolder{
return @"URLCACHE";
}
#pragma mark 当前代码只有替代品PNG图像
- (NSString *)mimeTypeForPath:(NSString *)originalPath{
return @"image/png";
}
- (void)setKeyArray:(NSArray *)keyArray{
_keyArray = keyArray;
}
@end
使用代码:
//
// ViewController.h
// DPWebViewLocalCacheDemo
//
// Created by yupeng xia on 2016/11/11.
// Copyright © 2016年 yupeng xia. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (weak,nonatomic)IBOutletUIWebView *myWebView;
@end
//
// ViewController.m
// DPWebViewLocalCacheDemo
//
// Created by yupeng xia on 2016/11/11.
// Copyright © 2016年 yupeng xia. All rights reserved.
//
#import "ViewController.h"
#import "DPLocalCache.h"
@interface ViewController ()<UIWebViewDelegate>{
}
//网络请求活动指示器
@property(nonatomic,strong)UIActivityIndicatorView* activityIndicatorView;
@end
@implementation ViewController
#pragma mark <----------View LifeCycle---------->
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
- (void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
}
-(void)dealloc{
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
DPLocalCache *urlCache = (DPLocalCache *)[NSURLCachesharedURLCache];
[urlCache deleteCacheFolder];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self createActivityIndicatorView];
self.myWebView.delegate =self;
DPLocalCache *urlCache = [[DPLocalCachealloc]initWithMemoryCapacity:20 *1024 *1024
diskCapacity:200 *1024 *1024
diskPath:nil
cacheTime:60*60*24 //每隔24小时更新一次数据
modeTybe:DOWNLOAD_MODE
subDirectory:@"dir"];
[NSURLCache setSharedURLCache:urlCache];
Reachability *reachability = [ReachabilityreachabilityForInternetConnection];
NetworkStatus internetStatus = [reachabilitycurrentReachabilityStatus];
if (internetStatus !=NotReachable) {//网络不可用,使用本地缓存
[self.myWebViewloadRequest:[NSURLRequestrequestWithURL:[NSURLURLWithString:@"https://github.com/xiayuqingfeng/DPWebViewLocalCache"]]];
NSUserDefaults *userDefaults = [NSUserDefaultsstandardUserDefaults];
BOOL firstStart = [userDefaultsboolForKey:@"firstStart"];
if (!firstStart) {
[userDefaults setBool:YESforKey:@"firstStart"];
// 延迟执行:
double delayInSeconds =0.5;
dispatch_time_t popTime =dispatch_time(DISPATCH_TIME_NOW, delayInSeconds *NSEC_PER_SEC);
dispatch_after(popTime,dispatch_get_main_queue(), ^(void){
UIAlertController *alertController = [UIAlertControlleralertControllerWithTitle:@"操作提示"message:@"网页加载完成->断网->重新打开客户端"preferredStyle:(UIAlertControllerStyleAlert)];
// 创建按钮
UIAlertAction *okAction = [UIAlertActionactionWithTitle:@"确定"style:(UIAlertActionStyleDefault)handler:nil];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YEScompletion:nil];
});
}
}else{
// 延迟执行:
double delayInSeconds =0.5;
dispatch_time_t popTime =dispatch_time(DISPATCH_TIME_NOW, delayInSeconds *NSEC_PER_SEC);
dispatch_after(popTime,dispatch_get_main_queue(), ^(void){
UIAlertController *alertController = [UIAlertControlleralertControllerWithTitle:@"提示"message:@"没有网络,浏览器使用本地网页缓存数据……"preferredStyle:(UIAlertControllerStyleAlert)];
// 创建按钮
__block typeof(self) bself =self;
UIAlertAction *okAction = [UIAlertActionactionWithTitle:@"确定"style:(UIAlertActionStyleDefault)handler:^(UIAlertAction *action) {
[bself.myWebViewloadRequest:[NSURLRequestrequestWithURL:[NSURLURLWithString:@"https://github.com/xiayuqingfeng/DPWebViewLocalCache"]]];
}];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YEScompletion:nil];
});
}
}
#pragma mark <----------UIWebViewDelegate---------->
//开始网络请求
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
//显示网页加载"Loading框"和"状态栏的网络活动标志"
[self webViewStartLoading];
return YES;
}
//开始加载
- (void)webViewDidStartLoad:(UIWebView *)webView{
[self webViewStartLoading];
}
//加载完成
- (void)webViewDidFinishLoad:(UIWebView *) webView{
//隐藏网页加载"Loading框"和"状态栏的网络活动标志"
[self webViewStopLoading];
}
//加载失败与错误
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
//隐藏网页加载"Loading框"和"状态栏的网络活动标志"
[self webViewStopLoading];
}
#pragma mark 网页加载"开始"或"结束",加载loading框,加载状态栏的网络活动标志
//创建网络请求"菊花"
- (void)createActivityIndicatorView{
if (!_activityIndicatorView) {
self.activityIndicatorView = [[UIActivityIndicatorViewalloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
_activityIndicatorView.color = [UIColordarkGrayColor];
_activityIndicatorView.center =self.view.center;
[self.viewbringSubviewToFront:_activityIndicatorView];
_activityIndicatorView.autoresizingMask =UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin;
[self.viewaddSubview:_activityIndicatorView];
}else{
[self.viewbringSubviewToFront:_activityIndicatorView];
}
}
//显示网页加载"Loading框"和"状态栏的网络活动标志"
- (void)webViewStartLoading{
//菊花开始旋转
if (_activityIndicatorView) {
[self.viewbringSubviewToFront:_activityIndicatorView];
[_activityIndicatorViewstartAnimating];
}
//打开状态栏的网络活动标志
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
//隐藏网页加载"Loading框"和"状态栏的网络活动标志"
- (void)webViewStopLoading{
//菊花停止旋转
if (_activityIndicatorView) {
[_activityIndicatorViewstopAnimating];
}
//关闭状态栏的网络活动标志
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
@end
三、上源码
开源库首页地址:DPWebViewLocalCache
源码地址:git clone address