`

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

    博客分类:
  • IOS
 
阅读更多

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

功能需求

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

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

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

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

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

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

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

设计实现:

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

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

@interface CacheItem : NSObject {
@public
id<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> {
@public
id<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)paramURLMustExpireInSeconds
updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache;

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

@end

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

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

- (BOOL)         download:(NSString *)paramURLAsString
urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds
updateExpiryDateIfInCache:(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内点击按钮,程序就从本地获取数据,比较快速的就显示出该网页了。

分享到:
评论

相关推荐

    带有本地缓存机制的http连接框架

    "带有本地缓存机制的http连接框架"是针对这种情况设计的一个高效、便捷的工具,它允许开发者轻松地发起HTTP请求,并且通过本地缓存策略优化数据加载速度,提高用户体验。 一、HTTP连接框架基础 HTTP连接框架是一种...

    SD卡,本地资源,网络图片的加载,使用缓存机制,防止OOM

    综上所述,通过结合SD卡存储、本地资源、网络图片加载,并巧妙运用缓存机制,我们可以有效防止Android应用出现OOM问题,实现流畅、高效的图片展示。同时,开发者应始终关注内存管理和优化,以提升应用的整体性能和...

    Android应用源码之优化增强的缓存机制(SimpleCache)-IT计算机-毕业设计.zip

    本项目“Android应用源码之优化增强的缓存机制(SimpleCache)”专注于探讨如何通过优化缓存策略来提高移动应用的性能。缓存机制的主要目标是减少网络请求,降低数据加载延迟,以及在有限的设备存储空间内合理管理资源...

    Android 优化增强的缓存机制(SimpleCache)-IT计算机-毕业设计.zip

    在Android应用开发中,缓存机制是提升性能和用户体验的关键技术之一。本项目"Android优化增强的缓存机制(SimpleCache)"聚焦于Android系统中如何有效地实现数据缓存,以提高应用的响应速度和减少网络资源的消耗。下面...

    listview的上拉加载下拉刷新+本地缓存+简单的头尾布局

    在本项目中,"listview的上拉加载下拉刷新+本地缓存+简单的头尾布局"是一个典型的移动应用功能实现,涉及到的技术点主要包括以下几个方面: 1. 上拉加载与下拉刷新:这是移动应用中常用的功能,提升用户体验,让...

    h5 本地缓存

    在现代Web开发中,HTML5(H5)引入了许多新的特性和功能,其中之一就是本地缓存机制,这对于提升用户体验和优化网页性能具有重要意义。本文将深入探讨H5的本地缓存技术,包括其原理、使用场景、优势以及如何在实践中...

    网络游戏-适用于移动应用程序行为和网络条件的缓存.zip

    这个名为“网络游戏-适用于移动应用程序行为和网络条件的缓存.zip”的压缩包包含了一份详细探讨这一主题的PDF文档。这份资料深入研究了如何利用缓存策略优化移动游戏在网络条件下的性能。 首先,我们要理解缓存的...

    Android应用商店设计

    综上所述,构建一个Android应用商店需要掌握Android SDK、Java/ Kotlin编程语言、SQLite数据库操作、网络编程、UI设计以及安全性等多个方面的知识。通过对"MyCloudyStore"项目的探索,开发者可以深入理解这些关键...

    Android应用源码之下载网络图片 (整合多线程、内存缓存、本地文件缓存~)-IT计算机-毕业设计.zip

    该Android应用源码示例是针对移动开发领域的一个经典课题,即如何在Android平台上实现高效地下载和管理网络图片。这个项目集成了多线程技术、内存缓存以及本地文件缓存策略,对于理解Android应用性能优化和资源管理...

    基于J2ME和PHP技术的移动计算应用解决方案.pdf

    文章中可能探讨了如何设计和实现这种缓存机制,以及如何处理缓存数据的更新和同步。 文章还提出了一种具体的应用实例——个人健康顾问系统。这个系统可能利用J2ME在移动设备上收集用户的健康数据,如运动量、饮食...

    &《HTML5&移动应用开发入门经典》

    3. 画布(Canvas)与SVG:HTML5的Canvas元素提供了一个图形绘制接口,通过JavaScript可以动态绘制2D图形。SVG(可缩放矢量图形)则支持创建高质量的矢量图像,两者都为网页带来了丰富的图形表现力。 4. 视频和音频:...

    Android 下载网络图片 (整合多线程、内存缓存、本地文件缓存~)-IT计算机-毕业设计.zip

    在Android应用开发中,下载网络图片是一个常见的需求,特别是在构建新闻阅读、社交或者电商类App时。本项目“Android 下载网络图片 (整合多线程、内存缓存、本地文件缓存~)”是一个针对这个需求的源码示例,旨在帮助...

    HTML5移动应用开发.zip

    总的来说,HTML5移动应用开发是一个涵盖广泛的技术领域,包括HTML5、CSS3、JavaScript的基础知识,以及相关的API、框架、工具和优化技巧。随着技术的不断发展,HTML5移动应用的潜力还在不断挖掘,为开发者提供了无限...

    移动应用Android 随手记记账应用源码.rar

    总的来说,“移动应用Android随手记记账应用源码”是一个深入学习Android应用开发的好素材。通过分析源码,开发者不仅可以掌握Android编程的基础知识,还能了解到如何设计和实现一个功能完备的记账应用,从而提升...

    一个简单的菜谱App《移动应用开发》课程项目.zip

    总结来说,开发一个简单的菜谱App涵盖了移动应用开发的多个核心知识点,包括软件架构设计、UI布局、数据管理、网络通信、状态管理和测试等。通过这样的项目,开发者不仅可以掌握基本的编程技能,还能锻炼解决问题和...

    面向分布式系统的高性能缓存管理软件设计及应用.pdf

    本文提出的是一种创新的缓存管理框架,该框架利用三级缓存架构,即数据库、远程缓存和应用本地缓存,以应对分布式系统下的高性能计算需求。 1. **三级缓存架构**: - **数据库**:作为基础数据源,负责存储原始...

    基于深度学习的智能移动边缘网络缓存.pdf

    在当今信息爆炸的时代,移动互联网用户对数据服务的需求日益增长,随之而来的挑战是对网络...此外,考虑到移动边缘网络中的多用户交互特性,开发能够更好地适应多用户场景的智能缓存策略,也是未来研究的一个重要方向。

    基于HTML5的移动船舶监控应用研究.zip

    这些本地存储机制可以缓存关键数据,确保在无网络时应用仍能正常运行。 再者,HTML5的Geolocation API可以方便地获取用户的地理位置信息,对于船舶监控应用,这意味着可以精确跟踪船舶的位置。结合地图API(如...

Global site tag (gtag.js) - Google Analytics