在手机应用程序开发中,为了减少与服务端的交互次数,加快用户的响应速度,一般都会在iOS设备中加一个缓存的机制,前面一篇文章介绍了iOS设备的内存缓存,这篇文章将设计一个本地缓存的机制。

功能需求

这个缓存机制满足下面这些功能。

1、可以将数据缓存到本地磁盘。

2、可以判断一个资源是否已经被缓存。如果已经被缓存,在请求相同的资源,先到本地磁盘搜索。

3、可以判断文件缓存什么时候过期。这里为了简单起见这里,我们在请求url资源的时候,给每次请求的文件设定一个过期的时间。

4、可以实现:如果文件已经被缓存,而且没有过期,这将本地的数据返回,否则重新请求url。

5、可以实现:如果文件下载不成功或者下载没有完成,下次打开程序的时候,移除这些没有成功或者没有下载完成的文件。

6、可以实现:同时请求或者下载多个资源。

设计实现:

1、设计一个CacheItem类,用来请求一个web连接,它的一个实例表示一个缓存项。这个CacheItem类,需要一个url创建一个NSURLConnection,去请求web资源。使用CacheItem类主要用来请求web资源。

/* ---------缓存项-------------- */

@interface CacheItem : NSObject {@publicid<CacheItemDelegate>delegate;//web地址  NSString              *remoteURL;@private//是否正在下载  BOOL                  isDownloading;//NSMutableData对象  NSMutableData         *connectionData;//NSURLConnection对象  NSURLConnection       *connection;}

/* -------------------------- */

@property (nonatomic, retain) id<CacheItemDelegate>delegate;@property (nonatomic, retain) NSString  *remoteURL;@property (nonatomic, assign) BOOL      isDownloading;@property (nonatomic, retain) NSMutableData *connectionData;@property (nonatomic, retain) NSURLConnection *connection;

/* ----------开始下载方法----------- */

- (BOOL) startDownloadingURL:(NSString *)paramRemoteURL;

@end

2、在NSURLConnection开始请求之前,调用CachedDownloadManager类,来搜索和管理本地的缓存文件。将缓存文件的情况保存到一个字典类中。这个字典设计如下:

{  "http://www.cnn.com" =     {    DownloadEndDate = "2011-08-02 07:51:57 +0100";    DownloadStartDate = "2011-08-02 07:51:55 +0100";    ExpiresInSeconds = 20;    ExpiryDate = "2011-08-02 07:52:17 +0100";    LocalURL = "/var/mobile/Applications/ApplicationID/Documents/                httpwww.cnn.com.cache";  };  "http://www.baidu.com" =     {    DownloadEndDate = "2011-08-02 07:51:49 +0100";    DownloadStartDate = "2011-08-02 07:51:44 +0100";    ExpiresInSeconds = 20;    ExpiryDate = "2011-08-02 07:52:09 +0100";    LocalURL = "/var/mobile/Applications/ApplicationID/Documents/                httpwww.oreilly.com.cache";  };}

上面这个字典里面嵌套了字典。里面那层字典表示一个缓存项的缓存信息:下载结束时间、下载开始时间、缓存有效时间、缓存过期时间、缓存到本地的路径。

下面看下CachedDownloadManager类。用它来实现和封装我们的缓存策略。

/* -----------CachedDownloadManager-------------- */

@interface CachedDownloadManager : NSObject <CacheItemDelegate> {@publicid<CachedDownloadManagerDelegate>delegate;@private//记录缓存数据的字典  NSMutableDictionary                *cacheDictionary;//缓存的路径  NSString                           *cacheDictionaryPath;}

@property (nonatomic, assign) id<CachedDownloadManagerDelegate>delegate;

@property (nonatomic, copy) NSMutableDictionary *cacheDictionary;

@property (nonatomic, retain) NSString *cacheDictionaryPath;

/* 保持缓存字典 */

- (BOOL) saveCacheDictionary;

/* 公有方法:下载 */

- (BOOL)         download:(NSString *)paramURLAsString   urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSecondsupdateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache;

/* -------------------------- */

@end

从上面代码可以看出,这个管理缓存的类中,有一个缓存字典:cacheDictionary,用来表示所有资源的缓存情况;cacheDictionaryPath用来表示缓存的路径;saveCacheDictionary用来将缓存字典归档到本地文件中。download:urlMustExpireInSeconds:updateExpiryDateIfInCache是一个公共接口,通过传递url、缓存过期时间、是否更新缓存过期时间三个参数来方便的使用,实现我们的缓存策略。

3、如果这个文件已经被下载,而且没有过期,则从本地获取文件的数据。如果文件已经过期,则重新下载。我们通过download:urlMustExpireInSeconds:updateExpiryDateIfInCache方法来实现,主要看这个方法的代码:

- (BOOL)         download:(NSString *)paramURLAsString   urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSecondsupdateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache{

  BOOL result = NO;

if (self.cacheDictionary == nil ||      [paramURLAsString length] ==0){return(NO);  }

  paramURLAsString = [paramURLAsString lowercaseString];//根据url,从字典中获取缓存项的相关数据  NSMutableDictionary *itemDictionary =   [self.cacheDictionary objectForKey:paramURLAsString];

/* 使用下面这些变量帮助我们理解缓存逻辑 *///文件是否已经被缓存  BOOL    fileHasBeenCached = NO;//缓存是否过期  BOOL    cachedFileHasExpired = NO;//缓存文件是否存在  BOOL    cachedFileExists = NO;//缓存文件能否被加载  BOOL    cachedFileDataCanBeLoaded = NO;//缓存文件数据  NSData  *cachedFileData = nil;//缓存文件是否完全下载  BOOL    cachedFileIsFullyDownloaded = NO;//缓存文件是否已经下载  BOOL    cachedFileIsBeingDownloaded = NO;//过期时间  NSDate    *expiryDate = nil;//下载结束时间  NSDate    *downloadEndDate = nil;//下载开始时间  NSDate    *downloadStartDate = nil;//本地缓存路径  NSString  *localURL = nil;//有效时间  NSNumber  *expiresInSeconds = nil;  NSDate    *now = [NSDate date];

if (itemDictionary != nil){    fileHasBeenCached = YES;  }//如果文件已经被缓存,则从缓存项相关数据中获取相关的值if (fileHasBeenCached == YES){

    expiryDate = [itemDictionary                   objectForKey:CachedKeyExpiryDate];

    downloadEndDate = [itemDictionary                       objectForKey:CachedKeyDownloadEndDate];

    downloadStartDate = [itemDictionary                         objectForKey:CachedKeyDownloadStartDate];

    localURL = [itemDictionary                objectForKey:CachedKeyLocalURL];

    expiresInSeconds = [itemDictionary                        objectForKey:CachedKeyExpiresInSeconds];//如果下载开始和结束时间不为空,表示文件全部被下载if (downloadEndDate != nil &&         downloadStartDate != nil){      cachedFileIsFullyDownloaded = YES;    }

/* 如果expiresInSeconds不为空,downloadEndDate为空,表示文件已经正在下载 */if (expiresInSeconds != nil &&        downloadEndDate == nil){      cachedFileIsBeingDownloaded = YES;    }

/* 判断缓存是否过期 */if (expiryDate != nil &&        [now timeIntervalSinceDate:expiryDate] >0.0){      cachedFileHasExpired = YES;    }

if (cachedFileHasExpired == NO){/* 如果缓存文件没有过期,加载缓存文件,并且更新过期时间 */      NSFileManager *fileManager = [[NSFileManager alloc] init];

if ([fileManager fileExistsAtPath:localURL] == YES){        cachedFileExists = YES;        cachedFileData = [NSData dataWithContentsOfFile:localURL];if (cachedFileData != nil){          cachedFileDataCanBeLoaded = YES;        } /* if (cachedFileData != nil){ */      } /* if ([fileManager fileExistsAtPath:localURL] == YES){ */

      [fileManager release];

/* 更新缓存时间 */

if (paramUpdateExpiryDateIfInCache == YES){

        NSDate *newExpiryDate =         [NSDate dateWithTimeIntervalSinceNow:         paramURLMustExpireInSeconds];

        NSLog(@"Updating the expiry date from %@ to %@.",               expiryDate,               newExpiryDate);

        [itemDictionary setObject:newExpiryDate                           forKey:CachedKeyExpiryDate];

        NSNumber *expires =         [NSNumber numberWithFloat:paramURLMustExpireInSeconds];

        [itemDictionary setObject:expires                           forKey:CachedKeyExpiresInSeconds];      }

    } /* if (cachedFileHasExpired == NO){ */

  }

if (cachedFileIsBeingDownloaded == YES){    NSLog(@"这个文件已经正在下载...");return(YES);  }

if (fileHasBeenCached == YES){

if (cachedFileHasExpired == NO &&        cachedFileExists == YES &&        cachedFileDataCanBeLoaded == YES &&        [cachedFileData length] >0&&        cachedFileIsFullyDownloaded == YES){

/* 如果文件有缓存而且没有过期 */

      NSLog(@"文件有缓存而且没有过期.");

      [self.delegate        cachedDownloadManagerSucceeded:self       remoteURL:[NSURL URLWithString:paramURLAsString]       localURL:[NSURL URLWithString:localURL]       aboutToBeReleasedData:cachedFileData       isCachedData:YES];

return(YES);

    } else {/* 如果文件没有被缓存,获取缓存失败 */      NSLog(@"文件没有缓存.");      [self.cacheDictionary removeObjectForKey:paramURLAsString];      [self saveCacheDictionary];    } /* if (cachedFileHasExpired == NO && */

  } /* if (fileHasBeenCached == YES){ */

/* 去下载文件 */

4、下面我们设计缓存项下载成功和失败的两个委托方法:

@protocol CacheItemDelegate <NSObject>//下载成功执行该方法- (void) cacheItemDelegateSucceeded  :(CacheItem *)paramSender  withRemoteURL:(NSURL *)paramRemoteURL  withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData;

//下载失败执行该方法- (void) cacheItemDelegateFailed  :(CacheItem *)paramSender  remoteURL:(NSURL *)paramRemoteURL  withError:(NSError *)paramError;

@end

当我们下载成功的时候,修改缓存字典中的下载时间,表示已经下载完成,而且需要将请求的资源数据缓存到本地:

//缓存项的委托方法- (void) cacheItemDelegateSucceeded:(CacheItem *)paramSender         withRemoteURL:(NSURL *)paramRemoteURL        withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData{

//从缓存字典中获取该缓存项的相关数据  NSMutableDictionary *dictionary =   [self.cacheDictionary objectForKey:[paramRemoteURL absoluteString]];//取当前时间  NSDate *now = [NSDate date];//获取有效时间  NSNumber *expiresInSeconds = [dictionary                                 objectForKey:CachedKeyExpiresInSeconds];//转换成NSTimeInterval  NSTimeInterval expirySeconds = [expiresInSeconds floatValue];//修改字典中缓存项的下载结束时间  [dictionary setObject:[NSDate date]                 forKey:CachedKeyDownloadEndDate];//修改字典中缓存项的缓存过期时间  [dictionary setObject:[now dateByAddingTimeInterval:expirySeconds]                 forKey:CachedKeyExpiryDate];//保存缓存字典  [self saveCacheDictionary];

  NSString *localURL = [dictionary objectForKey:CachedKeyLocalURL];

/* 将下载的数据保持到磁盘 */if ([paramAboutToBeReleasedData writeToFile:localURL                                   atomically:YES] == YES){    NSLog(@"缓存文件到磁盘成功.");  } else{    NSLog(@"缓存文件到磁盘失败.");  }//执行缓存管理的委托方法  [self.delegate    cachedDownloadManagerSucceeded:self   remoteURL:paramRemoteURL   localURL:[NSURL URLWithString:localURL]   aboutToBeReleasedData:paramAboutToBeReleasedData   isCachedData:NO];

}

如果下载失败我们需要从缓存字典中移除改缓存项:

//缓存项失败失败的委托方法- (void) cacheItemDelegateFailed:(CacheItem *)paramSender                       remoteURL:(NSURL *)paramRemoteURL                       withError:(NSError *)paramError{

/* 从缓存字典中移除缓存项,并发送一个委托 */

if (self.delegate!= nil){

    NSMutableDictionary *dictionary =     [self.cacheDictionary      objectForKey:[paramRemoteURL absoluteString]];

    NSString *localURL = [dictionary                           objectForKey:CachedKeyLocalURL];

    [self.delegate     cachedDownloadManagerFailed:self     remoteURL:paramRemoteURL     localURL:[NSURL URLWithString:localURL]     withError:paramError];  }

  [self.cacheDictionary    removeObjectForKey:[paramRemoteURL absoluteString]];

}

5、加载缓存字典的时候,我们可以将没有下载完成的文件移除:

    NSString *documentsDirectory =     [self documentsDirectoryWithTrailingSlash:YES];//生产缓存字典的路径    cacheDictionaryPath =     [[documentsDirectory       stringByAppendingString:@"CachedDownloads.dic"] retain];//创建一个NSFileManager实例    NSFileManager *fileManager = [[NSFileManager alloc] init];//判断是否存在缓存字典的数据if ([fileManager          fileExistsAtPath:self.cacheDictionaryPath] == YES){        NSLog(self.cacheDictionaryPath);//加载缓存字典中的数据      NSMutableDictionary *dictionary =       [[NSMutableDictionary alloc]        initWithContentsOfFile:self.cacheDictionaryPath];

      cacheDictionary = [dictionary mutableCopy];

      [dictionary release];

//移除没有下载完成的缓存数据      [self removeCorruptedCachedItems];

    } else {//创建一个新的缓存字典      NSMutableDictionary *dictionary =       [[NSMutableDictionary alloc] init];

      cacheDictionary = [dictionary mutableCopy];

      [dictionary release];

    }

    [fileManager release];

这样就基本上完成了我们需要的功能,下面看看我们如何使用我们设计的缓存功能。

例子场景:

我们用一个UIWebView来显示stackoverflow这个网站,我们在这个网站的内容缓存到本地20秒,如果在20秒内用户去请求该网站,则从本地文件中获取内容,否则过了20秒,则重新获取数据,并缓存到本地。

在界面上拖放一个button和一个webview控件,如下图。

这样我们可以很方便使用前面定义好的类。我们在viewDidLoad 中实例化一个CachedDownloadManager,并设置它的委托为self。当下载完成的时候,执行CachedDownloadManager的下载成功的委托方法。

- (void)viewDidLoad {  [super viewDidLoad];    [self setTitle:@"本地缓存测试"];  CachedDownloadManager *newManager = [[CachedDownloadManager alloc] init];  self.downloadManager = newManager;  [newManager release];  [self.downloadManager setDelegate:self];

}

在button的点击事件中加入下面代码,请求stackoverflow :

static NSString *url =@"http://stackoverflow.com";

  [self.downloadManager download:url      urlMustExpireInSeconds:20.0f    updateExpiryDateIfInCache:YES];

上面的代码表示将这个stackoverflow的缓存事件设置为20s,并且如果在20s内有相同的请求,则从本地获取stackoverflow的内容数据。updateExpiryDateIfInCache设置为yes表示:在此请求的时候,缓存时间又更新为20s,类似我们的session。如果设置成no,则第一次请求20s之后,该缓存就过期。

请求完成之后会执行CachedDownloadManager的委托方法。我们将数据展示在uiwebview中,代码如下:

- (void) cachedDownloadManagerSucceeded:(CachedDownloadManager *)paramSender                              remoteURL:(NSURL *)paramRemoteURL                               localURL:(NSURL *)paramLocalURL                  aboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData                           isCachedData:(BOOL)paramIsCachedData{  

    [webview loadData:paramAboutToBeReleasedData MIMEType:@"text/html" textEncodingName:@"UTF-8" baseURL:[NSURL URLWithString:@"http://stackoverflow.com"]];  }

这样我们就实现了20s的缓存。

效果:

第一次点击测试按钮:

20s内点击按钮,程序就从本地获取数据,比较快速的就显示出该网页了。

总结:

本文通过代码和实例设计了一个iPhone应用程序本地缓存的方案。当然这个方案不是最好的,如果你有更好的思路,欢迎告诉我。

作者:朱祁林
出处:http://zhuqil.cnblogs.com 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载于:https://www.cnblogs.com/huanglong/archive/2013/06/03/3114805.html

设计一个移动应用的本地缓存机制(转)相关推荐

  1. 设计一个移动应用的本地缓存机制

    在手机应用程序开发中,为了降低与服务端的交互次数,加快用户的响应速度,一般都会在iOS设备中加一个缓存的机制,前面一篇文章介绍了iOS设备的内存缓存.这篇文章将设计一个本地缓存的机制. 功能需求 这个 ...

  2. 如何设计一个牛逼的本地缓存

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:ksfzhaohui juejin.im/post/5dd9 ...

  3. 如何设计一个牛逼的本地缓存!

    来源:ksfzhaohui | http://dwz.win/Ws4 最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存:一级缓存相对来说比较简单,功能比较齐全的 ...

  4. 我设计了一个牛逼的本地缓存!

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 作者:k ...

  5. 多任务学习,如何设计一个更好的参数共享机制?| AAAI 2020

    2019-12-26 05:44:43 作者 | 孙天祥 编辑 | 刘萍 原文标题:稀疏共享:当多任务学习遇见彩票假设 本文介绍了复旦大学邱锡鹏团队在AAAI 2020 上录用的一篇关于多任务学习的工 ...

  6. GUAVA本地缓存01_概述、优缺点、创建方式、回收机制、监听器、统计、异步锁定

    文章目录 ①. 本地缓存 - 背景 ②. 本地缓存 - 优缺点 ③. Guava Cache介绍 ④. Guava - 三种创建方式 ⑤. Guava - 如何回收缓存 ⑥. Guava - 移除监听 ...

  7. 本地缓存-loadingCache

    本地缓存 缓存大概是一个不能再熟悉的话题,今天的主要内容是分享一下我们公司使用比较多的本地缓存 loadingcache,同时也是自己使用过程中的一些探索的分享,其背后的架构其实就是Guava cac ...

  8. 实现 Java 本地缓存

    缓存,我相信大家对它一定不陌生,在项目中,缓存肯定是必不可少的.市面上有非常多的缓存工具,比如 Redis.Guava Cache 或者 EHcache.对于这些工具,我想大家肯定都非常熟悉,所以今天 ...

  9. java几点钟_实现 Java 本地缓存,该从这几点开始

    缓存,我相信大家对它一定不陌生,在项目中,缓存肯定是必不可少的.市面上有非常多的缓存工具,比如 Redis.Guava Cache 或者 EHcache. 对于这些工具,我想大家肯定都非常熟悉,所以今 ...

  10. Guava 本地缓存使用教程

    文章目录 准备工作 创建缓存 使用缓存 Cache 读取缓存 LoadingCache 读取缓存 修改缓存 Cache 修改缓存 LoadingCache 修改缓存 其他方法 在前面的文章 Sprin ...

最新文章

  1. LeetCode Compare Version Numbers(版本比较)
  2. iBatis resultMap出错 The error happened while setting a property on the result object 解决办法
  3. 中文只占一个字符_一文搞懂字符和字节的含义
  4. spring集成多个rabbitMQ
  5. 启动、内存、卡顿三大分析,用户体验就用它?
  6. React组件复用的方式
  7. 必须掌握的Java基础知识(二)
  8. 通达OA 太牛了!工作流表单设计中级联菜单原来可以这样实现(图文)
  9. Multisim14仿真使用汇总
  10. 测试员一份工作坚持多久跳槽,才能完美提升薪资?
  11. shell脚本合集2
  12. 画业务逻辑流程图后的感想
  13. 【名词解释】电信技术名词解释大全
  14. Postman是什么?
  15. 20220720学习反思
  16. 网页中这 10 种字体的运用方式,不会让人觉得 Low
  17. L-Lactate用途和性质,以及分析测定方法
  18. zzulioj 1825: 会长爱数学 (求循环节)
  19. 单片机无线模块编码和解码c语言,单片机编码 无线模块发送与接收 程序
  20. ​基于全新用户体验的NetAlly EtherScope nXG来了,EXG-200炒鸡厉害。

热门文章

  1. 2021回顾 | AI 领域十大研究趋势及必读论文
  2. EMNLP 2021 投稿FAQ
  3. 从 0 开始机器学习 - 机器学习系统的设计与误差分析
  4. 干货 | Github标星近3w,热榜第一,如何用Python实现所有算法和一些神经网络模型...
  5. 论文赏析[NAACL19]一个更好更快更强的序列标注成分句法分析器
  6. 数据科学包8-pandas高级内容之聚合统计
  7. python之django中models学习总结
  8. 经验之谈:程序员应该如何学好大数据技术
  9. 空降新书榜,霸占前三甲,还有什么是这些书做不到的?!
  10. Tesler去世丨你逃不过复制粘贴,同样也逃不过Tesler定律