?在手機應用程序開發中,為了降低與服務端的交互次數,加快用戶的響應速度,一般都會在iOS設備中加一個緩存的機制,前面一篇文章介紹了iOS設備的內存緩存。這篇文章將設計一個本地緩存的機制。
功能需求
這個緩存機制滿足以下這些功能。
1、能夠將數據緩存到本地磁盤。
2、能夠推斷一個資源是否已經被緩存。假設已經被緩存。在請求同樣的資源。先到本地磁盤搜索。
3、能夠推斷文件緩存什么時候過期。這里為了簡單起見這里,我們在請求url資源的時候。給每次請求的文件設定一個過期的時間。
4、能夠實現:假設文件已經被緩存,并且沒有過期。這將本地的數據返回,否則又一次請求url。
5、能夠實現:假設文件下載不成功或者下載沒有完畢,下次打開程序的時候,移除這些沒有成功或者沒有下載完畢的文件。
6、能夠實現:同一時候請求或者下載多個資源。
設計實現:
1、設計一個CacheItem類,用來請求一個web連接,它的一個實例表示一個緩存項。
這個CacheItem類,須要一個url創建一個NSURLConnection,去請求web資源。
使用CacheItem類主要用來請求web資源。
[plain]?view plaincopy
/*?---------緩存項--------------?*/?? ?? @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類,來搜索和管理本地的緩存文件。將緩存文件的情況保存到一個字典類中。
這個字典設計例如以下:
[plain]?view plaincopy
{?? ??"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類。
用它來實現和封裝我們的緩存策略。?
?
[plain]?view plaincopy
/*?-----------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方法來實現,主要看這種方法的代碼:
[plain]?view plaincopy
/*?---------下載--------------?*/?? ?? -?(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){?*/?? ???? ??/*?去下載文件?*/?? ???? ???? ??NSNumber?*expires?=??? ??[NSNumber?numberWithFloat:paramURLMustExpireInSeconds];?? ???? ??NSMutableDictionary?*newDictionary?=??? ??[[[NSMutableDictionary?alloc]?init]?autorelease];?? ???? ??[newDictionary?setObject:expires??? ????????????????????forKey:CachedKeyExpiresInSeconds];?? ???? ???? ??localURL?=?[paramURLAsString?? ??????????????stringByAddingPercentEscapesUsingEncoding:?? ??????????????NSUTF8StringEncoding];?? ???? ??localURL?=?[localURL?stringByReplacingOccurrencesOfString:@"://"?? ?????????????????????????????????????????????????withString:@""];?? ???? ??localURL?=?[localURL?stringByReplacingOccurrencesOfString:@"/"?? ?????????????????????????????????????????????????withString:@"{1}quot;];?? ???? ??localURL?=?[localURL?stringByAppendingPathExtension:@"cache"];?? ???? ??NSString?*documentsDirectory?=??? ??[self?documentsDirectoryWithTrailingSlash:NO];?? ???? ??localURL?=?[documentsDirectory??? ??????????????stringByAppendingPathComponent:localURL];?? ???? ??[newDictionary?setObject:localURL?? ????????????????????forKey:CachedKeyLocalURL];?? ???? ??[newDictionary?setObject:now?? ????????????????????forKey:CachedKeyDownloadStartDate];?? ???? ??[self.cacheDictionary?setObject:newDictionary?? ???????????????????????????forKey:paramURLAsString];?? ???? ??[self?saveCacheDictionary];?? ???? ??CacheItem?*item?=?[[[CacheItem?alloc]?init]?autorelease];?? ??[item?setDelegate:self];?? ??[item?startDownloadingURL:paramURLAsString];?? ???? ??return(result);?? ???? }?? 4、以下我們設計緩存項下載成功和失敗的兩個托付方法:
[plain]?view plaincopy
@protocol?CacheItemDelegate?<NSObject>?? //下載成功運行該方法?? -?(void)?cacheItemDelegateSucceeded?? ??:(CacheItem?*)paramSender?? ??withRemoteURL:(NSURL?*)paramRemoteURL?? ??withAboutToBeReleasedData:(NSData?*)paramAboutToBeReleasedData;?? ?? //下載失敗運行該方法?? -?(void)?cacheItemDelegateFailed?? ??:(CacheItem?*)paramSender?? ??remoteURL:(NSURL?*)paramRemoteURL?? ??withError:(NSError?*)paramError;?? ?? @end??
?? 當我們下載成功的時候,改動緩存字典中的下載時間,表示已經下載完畢。并且須要將請求的資源數據緩存到本地:
[plain]?view plaincopy
//緩存項的托付方法?? -?(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];?? ???? ???? }?? 假設下載失敗我們須要從緩存字典中移除改緩存項:
[plain]?view plaincopy
//緩存項失敗失敗的托付方法?? -?(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、載入緩存字典的時候,我們能夠將沒有下載完畢的文件移除:
[plain]?view plaincopy
//初始化緩存字典?? ??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];?? ?????? ??}?? 這樣就基本上完畢了我們須要的功能。以下看看我們怎樣使用我們設計的緩存功能。 樣例場景:
??? 我們用一個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:urlurlMustExpireInSeconds:
20.0fupdateExpiryDateIfInCache:YES];
??? 上面的代碼表示將這個stackoverflow的緩存事件設置為20s。而且假設在20s內有同樣的請求,則從本地獲取stackoverflow的內容數據。updateExpiryDateIfInCache設置為yes表示:在此請求的時候。緩存時間又更新為20s。類似我們的session。假設設置成no。則第一次請求20s之后。該緩存就過期。
??? 請求完畢之后會運行CachedDownloadManager的托付方法。我們將數據展示在uiwebview中,代碼例如以下:
- (
void) cachedDownloadManagerSucceeded:(CachedDownloadManager *)paramSenderremoteURL:(NSURL *)paramRemoteURLlocalURL:(NSURL *)paramLocalURLaboutToBeReleasedData:(NSData *)paramAboutToBeReleasedDataisCachedData:(BOOL)paramIsCachedData{ [webview loadData:paramAboutToBeReleasedData MIMEType:
@"text/html" textEncodingName:
@"UTF-8" baseURL:[NSURL URLWithString:
@"http://stackoverflow.com"]];
}
這樣我們就實現了20s的緩存。
效果:
第一次點擊測試button:
?
20s內點擊button,程序就從本地獲取數據。比較高速的就顯示出該網頁了。
?
總結:
??? 本文通過代碼和實例設計了一個iPhone應用程序本地緩存的方案。
當然這個方法不是最好的,假設你有更好的思路。歡迎告訴我。
總結
以上是生活随笔為你收集整理的设计一个移动应用的本地缓存机制的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。