iOS开发之缓存(二):本地缓存机制

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

功能需求

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

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

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

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

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

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

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

设计实现:

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

复制代码
 1 /* ---------缓存项-------------- */  
 2   
 3 @interface CacheItem : NSObject {  
 4 @public  
 5   id<CacheItemDelegate> delegate;  
 6     //web地址  
 7   NSString              *remoteURL;  
 8 @private  
 9     //是否正在下载  
10   BOOL                  isDownloading;  
11        //NSMutableData对象  
12   NSMutableData         *connectionData;  
13     //NSURLConnection对象  
14   NSURLConnection       *connection;  
15 }  
16   
17 /* -------------------------- */  
18   
19 @property (nonatomic, retain) id<CacheItemDelegate> delegate;  
20 @property (nonatomic, retain) NSString  *remoteURL;  
21 @property (nonatomic, assign) BOOL      isDownloading;  
22 @property (nonatomic, retain) NSMutableData *connectionData;  
23 @property (nonatomic, retain) NSURLConnection *connection;  
24   
25 /* ----------开始下载方法----------- */  
26   
27 - (BOOL) startDownloadingURL:(NSString *)paramRemoteURL;  
28   
29 @end  
复制代码



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

复制代码
 1 {  
 2   "http://www.cnn.com" =     {  
 3     DownloadEndDate = "2011-08-02 07:51:57 +0100";  
 4     DownloadStartDate = "2011-08-02 07:51:55 +0100";  
 5     ExpiresInSeconds = 20;  
 6     ExpiryDate = "2011-08-02 07:52:17 +0100";  
 7     LocalURL = "/var/mobile/Applications/ApplicationID/Documents/  
 8                 httpwww.cnn.com.cache";  
 9   };  
10   "http://www.baidu.com" =     {  
11     DownloadEndDate = "2011-08-02 07:51:49 +0100";  
12     DownloadStartDate = "2011-08-02 07:51:44 +0100";  
13     ExpiresInSeconds = 20;  
14     ExpiryDate = "2011-08-02 07:52:09 +0100";  
15     LocalURL = "/var/mobile/Applications/ApplicationID/Documents/  
16                 httpwww.oreilly.com.cache";  
17   };  
18 }  
19 
20  上面这个字典里面嵌套了字典。里面那层字典表示一个缓存项的缓存信息:下载结束时间、下载开始时间、缓存有效时间、缓存过期时间、缓存到本地的路径。
复制代码



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

 

复制代码
 1 /* -----------CachedDownloadManager-------------- */  
 2   
 3 @interface CachedDownloadManager : NSObject   
 4                                    <CacheItemDelegate> {  
 5 @public  
 6   id<CachedDownloadManagerDelegate>  delegate;  
 7 @private  
 8 //记录缓存数据的字典  
 9   NSMutableDictionary                *cacheDictionary;  
10                                        //缓存的路径  
11   NSString                           *cacheDictionaryPath;  
12 }  
13   
14   
15 @property (nonatomic, assign)   
16 id<CachedDownloadManagerDelegate> delegate;  
17   
18 @property (nonatomic, copy)   
19 NSMutableDictionary *cacheDictionary;  
20   
21 @property (nonatomic, retain)   
22 NSString *cacheDictionaryPath;  
23   
24   
25 /* 保持缓存字典 */  
26   
27 - (BOOL) saveCacheDictionary;  
28   
29 /* 公有方法:下载 */  
30   
31 - (BOOL)         download:(NSString *)paramURLAsString  
32    urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds  
33 updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache;  
34   
35 /* -------------------------- */  
36   
37 @end  
复制代码



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

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

复制代码
  1 /* ---------下载-------------- */  
  2   
  3 - (BOOL)         download:(NSString *)paramURLAsString  
  4    urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds  
  5 updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache{  
  6     
  7   BOOL result = NO;  
  8     
  9   if (self.cacheDictionary == nil ||  
 10       [paramURLAsString length] == 0){  
 11     return(NO);  
 12   }  
 13     
 14   paramURLAsString = [paramURLAsString lowercaseString];  
 15   //根据url,从字典中获取缓存项的相关数据  
 16   NSMutableDictionary *itemDictionary =   
 17   [self.cacheDictionary objectForKey:paramURLAsString];  
 18     
 19   /* 使用下面这些变量帮助我们理解缓存逻辑 */  
 20     //文件是否已经被缓存  
 21   BOOL    fileHasBeenCached = NO;  
 22     //缓存是否过期  
 23   BOOL    cachedFileHasExpired = NO;  
 24     //缓存文件是否存在  
 25   BOOL    cachedFileExists = NO;  
 26     //缓存文件能否被加载  
 27   BOOL    cachedFileDataCanBeLoaded = NO;  
 28     //缓存文件数据  
 29   NSData  *cachedFileData = nil;  
 30     //缓存文件是否完全下载  
 31   BOOL    cachedFileIsFullyDownloaded = NO;  
 32     //缓存文件是否已经下载  
 33   BOOL    cachedFileIsBeingDownloaded = NO;  
 34   //过期时间  
 35   NSDate    *expiryDate = nil;  
 36     //下载结束时间  
 37   NSDate    *downloadEndDate = nil;  
 38     //下载开始时间  
 39   NSDate    *downloadStartDate = nil;  
 40     //本地缓存路径  
 41   NSString  *localURL = nil;  
 42     //有效时间  
 43   NSNumber  *expiresInSeconds = nil;  
 44   NSDate    *now = [NSDate date];  
 45     
 46   if (itemDictionary != nil){  
 47     fileHasBeenCached = YES;  
 48   }  
 49   //如果文件已经被缓存,则从缓存项相关数据中获取相关的值  
 50   if (fileHasBeenCached == YES){  
 51       
 52     expiryDate = [itemDictionary   
 53                   objectForKey:CachedKeyExpiryDate];  
 54       
 55     downloadEndDate = [itemDictionary  
 56                        objectForKey:CachedKeyDownloadEndDate];  
 57       
 58     downloadStartDate = [itemDictionary  
 59                          objectForKey:CachedKeyDownloadStartDate];  
 60       
 61     localURL = [itemDictionary  
 62                 objectForKey:CachedKeyLocalURL];  
 63       
 64     expiresInSeconds = [itemDictionary  
 65                         objectForKey:CachedKeyExpiresInSeconds];  
 66     //如果下载开始和结束时间不为空,表示文件全部被下载  
 67     if (downloadEndDate != nil &&   
 68         downloadStartDate != nil){  
 69       cachedFileIsFullyDownloaded = YES;  
 70     }  
 71       
 72     /* 如果expiresInSeconds不为空,downloadEndDate为空,表示文件已经正在下载 */  
 73     if (expiresInSeconds != nil &&  
 74         downloadEndDate == nil){  
 75       cachedFileIsBeingDownloaded = YES;  
 76     }  
 77       
 78     /* 判断缓存是否过期 */  
 79     if (expiryDate != nil &&  
 80         [now timeIntervalSinceDate:expiryDate] > 0.0){  
 81       cachedFileHasExpired = YES;  
 82     }  
 83       
 84     if (cachedFileHasExpired == NO){  
 85       /* 如果缓存文件没有过期,加载缓存文件,并且更新过期时间 */  
 86       NSFileManager *fileManager = [[NSFileManager alloc] init];  
 87         
 88       if ([fileManager fileExistsAtPath:localURL] == YES){  
 89         cachedFileExists = YES;  
 90         cachedFileData = [NSData dataWithContentsOfFile:localURL];  
 91         if (cachedFileData != nil){  
 92           cachedFileDataCanBeLoaded = YES;  
 93         } /* if (cachedFileData != nil){ */  
 94       } /* if ([fileManager fileExistsAtPath:localURL] == YES){ */  
 95         
 96       [fileManager release];  
 97         
 98       /* 更新缓存时间 */  
 99         
100       if (paramUpdateExpiryDateIfInCache == YES){  
101           
102         NSDate *newExpiryDate =   
103         [NSDate dateWithTimeIntervalSinceNow:  
104          paramURLMustExpireInSeconds];  
105           
106         NSLog(@"Updating the expiry date from %@ to %@.",   
107               expiryDate,   
108               newExpiryDate);  
109           
110         [itemDictionary setObject:newExpiryDate  
111                            forKey:CachedKeyExpiryDate];  
112           
113         NSNumber *expires =   
114         [NSNumber numberWithFloat:paramURLMustExpireInSeconds];  
115           
116         [itemDictionary setObject:expires  
117                            forKey:CachedKeyExpiresInSeconds];  
118       }  
119         
120     } /* if (cachedFileHasExpired == NO){ */  
121       
122   }  
123     
124   if (cachedFileIsBeingDownloaded == YES){  
125     NSLog(@"这个文件已经正在下载...");  
126     return(YES);  
127   }  
128     
129   if (fileHasBeenCached == YES){  
130       
131     if (cachedFileHasExpired == NO &&  
132         cachedFileExists == YES &&  
133         cachedFileDataCanBeLoaded == YES &&  
134         [cachedFileData length] > 0 &&  
135         cachedFileIsFullyDownloaded == YES){  
136         
137       /* 如果文件有缓存而且没有过期 */  
138         
139       NSLog(@"文件有缓存而且没有过期.");  
140         
141       [self.delegate   
142        cachedDownloadManagerSucceeded:self  
143        remoteURL:[NSURL URLWithString:paramURLAsString]  
144        localURL:[NSURL URLWithString:localURL]  
145        aboutToBeReleasedData:cachedFileData  
146        isCachedData:YES];  
147         
148       return(YES);  
149         
150     } else {  
151       /* 如果文件没有被缓存,获取缓存失败 */  
152       NSLog(@"文件没有缓存.");  
153       [self.cacheDictionary removeObjectForKey:paramURLAsString];  
154       [self saveCacheDictionary];  
155     } /* if (cachedFileHasExpired == NO && */  
156       
157   } /* if (fileHasBeenCached == YES){ */  
158     
159   /* 去下载文件 */  
160     
161     
162   NSNumber *expires =   
163   [NSNumber numberWithFloat:paramURLMustExpireInSeconds];  
164     
165   NSMutableDictionary *newDictionary =   
166   [[[NSMutableDictionary alloc] init] autorelease];  
167     
168   [newDictionary setObject:expires   
169                     forKey:CachedKeyExpiresInSeconds];  
170     
171     
172   localURL = [paramURLAsString  
173               stringByAddingPercentEscapesUsingEncoding:  
174               NSUTF8StringEncoding];  
175     
176   localURL = [localURL stringByReplacingOccurrencesOfString:@"://"  
177                                                  withString:@""];  
178     
179   localURL = [localURL stringByReplacingOccurrencesOfString:@"/"  
180                                                  withString:@"{1}quot;];  
181     
182   localURL = [localURL stringByAppendingPathExtension:@"cache"];  
183     
184   NSString *documentsDirectory =   
185   [self documentsDirectoryWithTrailingSlash:NO];  
186     
187   localURL = [documentsDirectory   
188               stringByAppendingPathComponent:localURL];  
189     
190   [newDictionary setObject:localURL  
191                     forKey:CachedKeyLocalURL];  
192     
193   [newDictionary setObject:now  
194                     forKey:CachedKeyDownloadStartDate];  
195     
196   [self.cacheDictionary setObject:newDictionary  
197                            forKey:paramURLAsString];  
198     
199   [self saveCacheDictionary];  
200     
201   CacheItem *item = [[[CacheItem alloc] init] autorelease];  
202   [item setDelegate:self];  
203   [item startDownloadingURL:paramURLAsString];  
204     
205   return(result);  
206     
207 }  
复制代码


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

复制代码
 1 @protocol CacheItemDelegate <NSObject>  
 2 //下载成功执行该方法  
 3 - (void) cacheItemDelegateSucceeded  
 4   :(CacheItem *)paramSender  
 5   withRemoteURL:(NSURL *)paramRemoteURL  
 6   withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData;  
 7   
 8 //下载失败执行该方法  
 9 - (void) cacheItemDelegateFailed  
10   :(CacheItem *)paramSender  
11   remoteURL:(NSURL *)paramRemoteURL  
12   withError:(NSError *)paramError;  
13   
14 @end  
复制代码

  

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

复制代码
 1 //缓存项的委托方法  
 2 - (void) cacheItemDelegateSucceeded:(CacheItem *)paramSender  
 3          withRemoteURL:(NSURL *)paramRemoteURL  
 4         withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData{  
 5     
 6   //从缓存字典中获取该缓存项的相关数据  
 7   NSMutableDictionary *dictionary =   
 8   [self.cacheDictionary objectForKey:[paramRemoteURL absoluteString]];  
 9   //取当前时间  
10   NSDate *now = [NSDate date];  
11   //获取有效时间  
12   NSNumber *expiresInSeconds = [dictionary   
13                                 objectForKey:CachedKeyExpiresInSeconds];  
14   //转换成NSTimeInterval  
15   NSTimeInterval expirySeconds = [expiresInSeconds floatValue];  
16   //修改字典中缓存项的下载结束时间  
17   [dictionary setObject:[NSDate date]  
18                  forKey:CachedKeyDownloadEndDate];  
19   //修改字典中缓存项的缓存过期时间  
20   [dictionary setObject:[now dateByAddingTimeInterval:expirySeconds]  
21                  forKey:CachedKeyExpiryDate];  
22   //保存缓存字典  
23   [self saveCacheDictionary];  
24     
25   NSString *localURL = [dictionary objectForKey:CachedKeyLocalURL];  
26     
27   /* 将下载的数据保持到磁盘 */  
28   if ([paramAboutToBeReleasedData writeToFile:localURL  
29                                    atomically:YES] == YES){  
30     NSLog(@"缓存文件到磁盘成功.");  
31   } else{  
32     NSLog(@"缓存文件到磁盘失败.");  
33   }  
34   //执行缓存管理的委托方法  
35   [self.delegate   
36    cachedDownloadManagerSucceeded:self  
37    remoteURL:paramRemoteURL  
38    localURL:[NSURL URLWithString:localURL]  
39    aboutToBeReleasedData:paramAboutToBeReleasedData  
40    isCachedData:NO];  
41     
42     
43 }  
复制代码


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

复制代码
 1 //缓存项失败失败的委托方法  
 2 - (void) cacheItemDelegateFailed:(CacheItem *)paramSender  
 3                        remoteURL:(NSURL *)paramRemoteURL  
 4                        withError:(NSError *)paramError{  
 5     
 6   /* 从缓存字典中移除缓存项,并发送一个委托 */  
 7     
 8   if (self.delegate != nil){  
 9       
10     NSMutableDictionary *dictionary =   
11     [self.cacheDictionary   
12      objectForKey:[paramRemoteURL absoluteString]];  
13       
14     NSString *localURL = [dictionary   
15                           objectForKey:CachedKeyLocalURL];  
16       
17     [self.delegate  
18      cachedDownloadManagerFailed:self  
19      remoteURL:paramRemoteURL  
20      localURL:[NSURL URLWithString:localURL]  
21      withError:paramError];  
22   }  
23     
24   [self.cacheDictionary   
25    removeObjectForKey:[paramRemoteURL absoluteString]];  
26     
27 }  
复制代码


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

 

复制代码
 1 //初始化缓存字典  
 2   NSString *documentsDirectory =   
 3   [self documentsDirectoryWithTrailingSlash:YES];  
 4   //生产缓存字典的路径  
 5   cacheDictionaryPath =   
 6   [[documentsDirectory   
 7     stringByAppendingString:@"CachedDownloads.dic"] retain];  
 8   //创建一个NSFileManager实例  
 9   NSFileManager *fileManager = [[NSFileManager alloc] init];  
10   //判断是否存在缓存字典的数据  
11   if ([fileManager   
12        fileExistsAtPath:self.cacheDictionaryPath] == YES){  
13       NSLog(self.cacheDictionaryPath);  
14     //加载缓存字典中的数据  
15     NSMutableDictionary *dictionary =   
16     [[NSMutableDictionary alloc]   
17      initWithContentsOfFile:self.cacheDictionaryPath];  
18       
19     cacheDictionary = [dictionary mutableCopy];  
20       
21     [dictionary release];  
22       
23     //移除没有下载完成的缓存数据  
24     [self removeCorruptedCachedItems];  
25       
26   } else {  
27     //创建一个新的缓存字典  
28     NSMutableDictionary *dictionary =   
29     [[NSMutableDictionary alloc] init];  
30       
31     cacheDictionary = [dictionary mutableCopy];  
32       
33     [dictionary release];  
34       
35   }  
36 这样就基本上完成了我们需要的功能,下面看看我们如何使用我们设计的缓存功能。
复制代码



例子场景:

    我们用一个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内点击按钮,程序就从本地获取数据,比较快速的就显示出该网页了。

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值