美文网首页#iOS#HeminWon程序猿的人文与技术iOS开发
iOS中,关于UIWebView网页数据本地缓存原理和实际使用。

iOS中,关于UIWebView网页数据本地缓存原理和实际使用。

作者: 语文化及 | 来源:发表于2016-03-23 09:15 被阅读10882次

         最近作者做的项目中需要用到UIWebView的离线缓存功能,本来满心欢喜的想着在UIWebView的代理方法中看看有没有什么代理方法可以直接做到缓存的功能,结果还是太天真了,后来网上搜索了一下(主要参考了在code4app上面rusking作业对UIWebView离线浏览的代码实现(地址https://github.com/lzhlewis2015/UIWebViewLocalCache),研究的过程中也花了不少时间,所以想在这里把我的心得分享一下),发现可以使用NSURLCache这个类实现。原理就是大多数的网络请求都会先调用这个类中的- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request 这个方法,那我们只要重写这个类,就能达到本地缓存的目的了。

    下面是大致的逻辑

    1 判断请求中的request 是不是使用get方法,据资料显示一些本地请求的协议也会进到这个方法里面来,所以在第一部,要把不相关的请求排除掉。

    2 判断缓存文件夹里面是否存在该文件,如果存在,继续判断文件是否过期,如果过期,则删除。如果文件没有过期,则提取文件,然后组成NSCacheURLResponse返回到方法当中。

    3在有网络的情况下,如果文件夹中不存在该文件,则利用NSConnection这个类发网络请求,再把返回的data和response 数据本地化存储起来,然后组成NSCacheURLResponse返回到方法当中。

    4其中BaseTools和其他没有在本.m文件中定义的类为常用的工具类,这里不一一展开了。


    大致逻辑就这么多,话不多说,直接看代码实现(关键代码有注释):

    #import "CustomURLCache.h"

    #import "NSObject+Network.h"

    #import "BaseTools.h"

    @interface CustomURLCache(private)

    - (NSString *)cacheFolder;

    - (NSString *)cacheFilePath:(NSString *)file;

    - (NSString *)cacheRequestFileName:(NSString *)requestUrl;

    - (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl;

    - (NSCachedURLResponse *)dataFromRequest:(NSURLRequest *)request;

    - (void)deleteCacheFolder;

    @end

    @implementation CustomURLCache

    - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path cacheTime:(NSInteger)cacheTime {

    if (self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]) {

    //cacheTime 为你所希望本地缓存的时间(以秒计算,如果设为60,则60秒之后本地缓存文件过期)

    self.cacheTime = cacheTime;

    if (path)

    self.diskPath = path;

    else    

    self.diskPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

    self.responseDictionary = [NSMutableDictionary dictionaryWithCapacity:0];

    }

    return self;

    }//

    - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {

    // 这里判断如果请求方法不为GET的话 直接返回父类方法,系统本来怎么干的就让它怎么干

    if ([request.HTTPMethod compare:@"GET"] != NSOrderedSame) {

    return [super cachedResponseForRequest:request];

    }

    // 核心方法

    return [self dataFromRequest:request];

    }//

    - (void)removeAllCachedResponses {

    [super removeAllCachedResponses];

    [self deleteCacheFolder];

    }//

    - (void)removeCachedResponseForRequest:(NSURLRequest *)request {

    [super removeCachedResponseForRequest:request];

    NSString *url = request.URL.absoluteString;

    NSString *fileName = [self cacheRequestFileName:url];

    NSString *otherInfoFileName = [self cacheRequestOtherInfoFileName:url];

    NSString *filePath = [self cacheFilePath:fileName];

    NSString *otherInfoPath = [self cacheFilePath:otherInfoFileName];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    [fileManager removeItemAtPath:filePath error:nil];

    [fileManager removeItemAtPath:otherInfoPath error:nil];

    }//

    #pragma mark - custom url cache

    - (NSString *)cacheFolder {

    return @"URLCACHE";

    }//

    - (void)deleteCacheFolder {

    NSString *path = [NSString stringWithFormat:@"%@/%@", self.diskPath, [self cacheFolder]];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    [fileManager removeItemAtPath:path error:nil];

    }//

    - (NSString *)cacheFilePath:(NSString *)file {

    NSString *path = [NSString stringWithFormat:@"%@/%@", self.diskPath, [self cacheFolder]];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    BOOL isDir;

    if ([fileManager fileExistsAtPath:path isDirectory:&isDir] && isDir) {

    } else {

    [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];

    }

    return [NSString stringWithFormat:@"%@/%@", path, file];

    }//

    - (NSString *)cacheRequestFileName:(NSString *)requestUrl {

    //对传进来的url进行md5 加密 ,加密后变成32位字符串,作为文件名保存

    return [BaseTools md5Hash:requestUrl];

    }//

    - (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl {

    //同上

    return [BaseTools md5Hash:[NSString stringWithFormat:@"%@-otherInfo", requestUrl]];

    }//

    - (NSCachedURLResponse *)dataFromRequest:(NSURLRequest *)request {

    //此为GET的情况

    // 这方法会返回多次 每一次链接相同的url(有网络的情况下,部分网页如:(百度),它这个url里面可能内嵌了很多其他的url,那其他的url可能每次都不一样,所以返回的request.url.absluteString 都不一样,这样导致每次系统会根据absoluteStr 来创建文件,则会越来越多;但针对作业的App里面涉及到的网页链接不会这样。

    NSString *url = request.URL.absoluteString;

    //md5 加密

    NSString *fileName = [self cacheRequestFileName:url];

    NSString *otherInfoFileName = [self cacheRequestOtherInfoFileName:url];

    //filePath 用于保存网页数据

    NSString *filePath = [self cacheFilePath:fileName];

    //otherInfoPath  用于保存该url 对应的一些配置属性,如创建时间,MIMEType等。。

    NSString *otherInfoPath = [self cacheFilePath:otherInfoFileName];

    NSDate *date = [NSDate date];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    if ([fileManager fileExistsAtPath:filePath]) {

    // expire 为过期的

    BOOL expire = false;

    NSDictionary *otherInfo = [NSDictionary dictionaryWithContentsOfFile:[otherInfoPath stringByAppendingString:@".plist"]];

    //cacheTime  为磁盘缓存文件在硬盘中保存的时间

    //cacheTime 为0 时则永远不会过期

    if (self.cacheTime > 0) {

    NSInteger createTime = [[otherInfo objectForKey:@"time"] intValue];

    if (createTime + self.cacheTime < [date timeIntervalSince1970]) {

    expire = true;

    }

    }

    if (expire == false ) {

    NSLog(@"data from cache ...");

    //发现缓存文件夹里面有缓存在硬盘的文件

    NSData *data = [NSData dataWithContentsOfFile:filePath];

    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL

    MIMEType:[otherInfo objectForKey:@"MIMEType"]

    expectedContentLength:data.length

    textEncodingName:[otherInfo objectForKey:@"textEncodingName"]];

    NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data] ;

    return cachedResponse;

    } else {

    NSLog(@"cache expire ... ");

    //过期了要删除

    [fileManager removeItemAtPath:filePath error:nil];

    [fileManager removeItemAtPath:[NSString stringWithFormat:@"%@",[otherInfoPath stringByAppendingString:@".plist"]] error:nil];

    }

    }

    if (![self isReachability]) {

    return nil;

    }

    // 有网络的状态下进行内容的缓存

    __block NSCachedURLResponse *cachedResponse = nil;

    //sendAsynchronousRequest请求也要经过NSURLCache

    //如果没有response 和data 的话, 那字典对应的value 为true,方法直接返回nil(此链接不能使用缓存);

    id boolExsit = [self.responseDictionary objectForKey:url];

    if (boolExsit == nil) {

    [self.responseDictionary setValue:[NSNumber numberWithBool:TRUE] forKey:url];

    [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data,NSError *error)

    {

    // 如果有data 和response 返回的话

    if (response && data) {

    //因为cachesResponse 这个方法会被调用多次,所有dataFromrequest也会被调用多次,那如果服务器返回有response 和data的话,就把responDicionary 这个字典清空,并把对应的data写入,并把对应的data和response 构建成Cacheresponse 返回

    [self.responseDictionary removeObjectForKey:url];

    if (error) {

    NSLog(@"error : %@", error);

    NSLog(@"not cached: %@", request.URL.absoluteString);

    cachedResponse = nil;

    }

    NSLog(@"---");

    //save to cache

    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%f", [date timeIntervalSince1970]], @"time",

    response.MIMEType, @"MIMEType",

    response.textEncodingName, @"textEncodingName", nil];

    BOOL dictSuccess = [dict writeToFile:[otherInfoPath stringByAppendingString:@".plist"] atomically:YES];

    BOOL dataSuccess = [data writeToFile:filePath atomically:YES];

    if (!dictSuccess) {

    NSLog(@"字典失败");

    }

    if (!dataSuccess) {

    NSLog(@"data 失败");

    }

    cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data] ;

    }

    }];

    return cachedResponse;

    }

    return nil;

    } //

    @end


    鉴于挺多读者可能看不到demo的下载地址,这里再列一下

    https://github.com/lzhlewis2015/UIWebViewLocalCache

    相关文章

      网友评论

      • hypercode:你好这种方式能用在ajax实现的网页吗?动态 的资源能不能被缓存读出来呢
      • 54685FD78BDA:我再代码里加这个cache,加载网页并不会调cachedResponseForRequest啊
      • FengxinLi:请问一下楼主 我运行你的demo可以运行但是我用到我自己的程序里面都缓存不起?
        54685FD78BDA:我也是,你找到解决办法了吗
        FengxinLi: demo 也没运行起我网忘记关了
      • 若小北00:有没有方法只缓存图片、js和CSS不全部换成,这样每次依然跟服务端交互,但是js和css等不变的话就本地获取,或者利用NSURLProtocol,楼主有没有试过
      • f7598536adc8:楼主求Demo,谢谢,1132026302@qq.com
      • f7598536adc8:楼主求Demo,谢谢 1132026302@qq.com
      • 奔怕惰:求源码 937746658@qq.com
      • 叶小萌:能把源码发我邮箱一份吗? 最近在做web页面的缓存。。。 谢谢大神。。。邮箱是137084265@qq.com 谢谢楼主
        语文化及:@叶小萌 文中有demo下载地址
      • 木子尚武:楼主,利用loadRequest加载网络数据,中文乱码你怎么解决.loadRequest很鸡肋,不能指定编码格式
      • 不知其然i:楼主 求一份demo 1986530786@qq.com 谢谢:stuck_out_tongue:
        语文化及:@HEYMI https://github.com/lzhlewis2015/UIWebViewLocalCache
      • 2ca199aa2102:你好 请教你个问题 如果demo中的链接换成 我们合作方提供给我们的链接的话 缓存之后 再次打开应用的 页面会变得很大 部分图片缺失 这会不会是跟他们本身的链接有关系呢
        语文化及:@个别作曲家 demo中里的代码没有涉及到对网页代码的修改 ,如果页面显示不正常的话可能是由于网页链接的某些参数不能通过get请求得到 (代码仅处理了get请求 ) 所以在离线加载的时候由于之前没有缓存到这些参数导致页面显示异常
        2ca199aa2102:@语文化及 是的 第一次启动应用后等网页加载(正常显示)完并缓存成功后 退出应用程序 第二次 启动应用 共缓存了 四个链接 其中有三个是我司自己做的链接 还有一个是合作方提供给我们使用的 就是这个显示 页面变得很大
        语文化及:@个别作曲家 你运行的时候有等webview load完整个网页再退出吗?
      • 74cfff17808c:楼主在吗 求份demo
        语文化及:@CJS_2016 文中有demo地址
      • 语文化及:原来的源码地址已失效,我把代码上传到github了,地址如下
        https://github.com/lzhlewis2015/UIWebViewLocalCache
        :smile:
      • 魔法黛:求源码,谢谢楼主395911304@qq.com
      • 9a8c62edad02:楼主,用UIWebView加载html,实现离线缓存的demo,能发一份到这个邮箱嘛.多谢了649283213@qq.com
      • CarryTomato:楼主求源码. :pray: 380347653@qq.com
      • Jacob_Pan:请教楼主,为什么POST请求要过滤掉,如果离线状体只有 post的请求成后才能显示怎么办?
        语文化及:@Jacob_Pan 文章中使用get 来过滤其他比如file协议 你也可以尝试加上
      • 星河紫:求源码,谢谢楼主 847006301@qq.com
      • 建国1949:源码发一下吧,谢谢!736000756@qq.com
      • Kimball:老板要求缓存的网页带视频怎么办
        语文化及:@Kimball 有视频的用这个缓存不了哦
      • 天上飞的狒狒:大神求源码一份 516182396@qq.com
      • 我我我我我我你你你你:源码发一下,谢谢!365322619@qq.com
      • iOS_小绅士:求份demo研究下.谢谢.bank6243@163.com
      • Kimball:求源码:bovvge@163.com,谢谢楼主
      • Qson1:求源码 qiubite2013@126.com
      • 西门淋雨:求源码:谢谢楼主:417513676@qq.com
      • 57470cde90da:求源码,谢谢楼主362602879@qq.com
      • 998e4a3fbbdc:求源码,谢谢楼主m18701265016@163.com
      • SevenM:求源码,谢谢楼主1984796126@qq.com
      • 东方_未明:求一份源码学习一下, 谢谢, 支持WKWebView么? QQ 289978684
      • c885641d3fe7:有没有源码哦 能不能让学妹参考一下哦……
        c885641d3fe7:@啊啦啦啦啦啦 714499544@qq.com
      • 67c9ab5605de:demo地址 404?
      • 菜鸟晋升路:楼主,求给demo 404058563@qq.com 谢谢
      • 1e9d5b41b221:楼主,求一份源码 ,584535070@qq.com
      • 6b913c25c8a1:楼主,我也想要Demo,最近在弄webView,能否发我一份262306291@qq.com 谢了!!! :smile:
      • Ths:我现在可以做到的是第二次刷新后缓存当前页面,但是那些js css 图片什么的都没有缓存.我查看了它先前第一次加载网页的时候,是把css js 图片分成几个url下载下来的.这个是我最纳闷的地方了
      • Ths:我设置了,但是它不进入那个重写的方法,不知道为何
        语文化及:@Ths 暂时没办法判断更新,它只认url地址,如果这个url地址被储存过了,那它就直接读取本地问价加载到webView上面, 只能设置过期时间。
        Ths:@语文化及 成功,我知道原因了。iOS8后要在delegate里面设置。我只写在控制器里面的。对了,它这个怎么判断网页有没有更新啊
        语文化及:@Ths 你运行Demo 加载webView 成功不
      • Ths:楼主在吗
      • LANZHI不想每晚失眠:受教了,谢谢:smile:
        语文化及:@LANZHI不想每晚失眠 :smile::smile:
      • 是从什么盛:有没有源码
        语文化及:@是从什么 发了 你看看吧:blush:
        是从什么盛:@是从什么 407557715@qq.com谢谢 我刚看到
        语文化及:@是从什么 有 发你邮箱

      本文标题:iOS中,关于UIWebView网页数据本地缓存原理和实际使用。

      本文链接:https://www.haomeiwen.com/subject/mhbklttx.html