關於本地緩存

今天電面時被問到了UIWebView的本地緩存,以前未關注過webview的緩存,因此查了下博文html

原文連接:http://blog.csdn.net/zhuqilin0/article/details/6653532web

 

在手機應用程序開發中,爲了減小與服務端的交互次數,加快用戶的響應速度,通常都會在iOS設備中加一個緩存的機制,前面一篇文章介紹了iOS設備的內存緩存,這篇文章將設計一個本地緩存的機制。緩存

功能需求session

這個緩存機制知足下面這些功能。併發

一、能夠將數據緩存到本地磁盤。測試

二、能夠判斷一個資源是否已經被緩存。若是已經被緩存,在請求相同的資源,先到本地磁盤搜索。網站

三、能夠判斷文件緩存何時過時。這裏爲了簡單起見這裏,咱們在請求url資源的時候,給每次請求的文件設定一個過時的時間。ui

四、能夠實現:若是文件已經被緩存,並且沒有過時,這將本地的數據返回,不然從新請求url。atom

五、能夠實現:若是文件下載不成功或者下載沒有完成,下次打開程序的時候,移除這些沒有成功或者沒有下載完成的文件。url

六、能夠實現:同時請求或者下載多個資源。

設計實現:

一、設計一個CacheItem類,用來請求一個web鏈接,它的一個實例表示一個緩存項。這個CacheItem類,須要一個url建立一個NSURLConnection,去請求web資源。使用CacheItem類主要用來請求web資源。

[plain]  view plain copy
 
  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  

二、在NSURLConnection開始請求以前,調用CachedDownloadManager類,來搜索和管理本地的緩存文件。將緩存文件的狀況保存到一個字典類中。這個字典設計以下:

[plain]  view plain copy
 
  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. }  

 
 
 上面這個字典裏面嵌套了字典。裏面那層字典表示一個緩存項的緩存信息:下載結束時間、下載開始時間、緩存有效時間、緩存過時時間、緩存到本地的路徑。

   下面看下CachedDownloadManager類。用它來實現和封裝咱們的緩存策略。 

 

[plain]  view plain copy
 
  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、緩存過時時間、是否更新緩存過時時間三個參數來方便的使用,實現咱們的緩存策略。

三、若是這個文件已經被下載,並且沒有過時,則從本地獲取文件的數據。若是文件已通過期,則從新下載。咱們經過download:urlMustExpireInSeconds:updateExpiryDateIfInCache方法來實現,主要看這個方法的代碼:

[plain]  view plain copy
 
  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. }  

四、下面咱們設計緩存項下載成功和失敗的兩個委託方法:

[plain]  view plain copy
 
  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  


   當咱們下載成功的時候,修改緩存字典中的下載時間,表示已經下載完成,並且須要將請求的資源數據緩存到本地:

[plain]  view plain copy
 
  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. }  
   若是下載失敗咱們須要從緩存字典中移除改緩存項:
[plain]  view plain copy
 
  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. }  
五、加載緩存字典的時候,咱們能夠將沒有下載完成的文件移除:
[plain] view plain copy
 
  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.   }  

這樣就基本上完成了咱們須要的功能,下面看看咱們如何使用咱們設計的緩存功能。

例子場景:

    咱們用一個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應用程序本地緩存的方案。固然這個方案不是最好的,若是你有更好的思路,歡迎告訴我。

相關文章
相關標籤/搜索