你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> 設計一個iOS應用的本地緩存機制

設計一個iOS應用的本地緩存機制

編輯:IOS開發綜合

在手機應用程序開發中,為了減少與服務端的交互次數,加快用戶的響應速度,一般都會在iOS設備中加一個緩存的機制.

AD:2013雲計算架構師峰會課程資料下載


前面一篇文章介紹了iOS設備的內存緩存,這篇文章將設計一個本地緩存的機制。

功能需求

這個緩存機制滿足下面這些功能。

1、可以將數據緩存到本地磁盤。

2、可以判斷一個資源是否已經被緩存。如果已經被緩存,在請求相同的資源,先到本地磁盤搜索。

3、可以判斷文件緩存什麼時候過期。這裡為了簡單起見這裡,我們在請求url資源的時候,給每次請求的文件設定一個過期的時間。

4、可以實現:如果文件已經被緩存,而且沒有過期,這將本地的數據返回,否則重新請求url。

5、可以實現:如果文件下載不成功或者下載沒有完成,下次打開程序的時候,移除這些沒有成功或者沒有下載完成的文件。

6、可以實現:同時請求或者下載多個資源。

設計實現

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

  1. /* ---------緩存項-------------- */
  2. @interface CacheItem : NSObject {
  3. @public
  4. id delegate;
  5. //web地址
  6. NSString *remoteURL;
  7. @private
  8. //是否正在下載
  9. BOOL isDownloading;
  10. //NSMutableData對象
  11. NSMutableData *connectionData;
  12. //NSURLConnection對象
  13. NSURLConnection *connection;
  14. }
  15. /* -------------------------- */
  16. @property (nonatomic, retain) id delegate;
  17. @property (nonatomic, retain) NSString *remoteURL;
  18. @property (nonatomic, assign) BOOL isDownloading;
  19. @property (nonatomic, retain) NSMutableData *connectionData;
  20. @property (nonatomic, retain) NSURLConnection *connection;
  21. /* ----------開始下載方法----------- */
  22. - (BOOL) startDownloadingURL:(NSString *)paramRemoteURL;
  23. @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. }


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

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

      1. /* -----------CachedDownloadManager-------------- */
      2. @interface CachedDownloadManager : NSObject
      3. {
      4. @public
      5. id delegate;
      6. @private
      7. //記錄緩存數據的字典
      8. NSMutableDictionary *cacheDictionary;
      9. //緩存的路徑
      10. NSString *cacheDictionaryPath;
      11. }
      12. @property (nonatomic, assign)
      13. id delegate;
      14. @property (nonatomic, copy)
      15. NSMutableDictionary *cacheDictionary;
      16. @property (nonatomic, retain)
      17. NSString *cacheDictionaryPath;
      18. /* 保持緩存字典 */
      19. - (BOOL) saveCacheDictionary;
      20. /* 公有方法:下載 */
      21. - (BOOL) download:(NSString *)paramURLAsString
      22. urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds
      23. updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache;
      24. /* -------------------------- */
      25. @end
      26. 從上面代碼可以看出,這個管理緩存的類中,有一個緩存字典:cacheDictionary,用來表示所有資源的緩存情況;cacheDictionaryPath用來表示緩存的路徑;saveCacheDictionary用來將緩存字典歸檔到本地文件中。download:urlMustExpireInSeconds:updateExpiryDateIfInCache是一個公共接口,通過傳遞url、緩存過期時間、是否更新緩存過期時間三個參數來方便的使用,實現我們的緩存策略。



        3、如果這個文件已經被下載,而且沒有過期,則從本地獲取文件的數據。如果文件已經過期,則重新下載。我們通過download:urlMustExpireInSeconds:updateExpiryDateIfInCache方法來實現,主要看這個方法的代碼:

        1. /* ---------下載-------------- */
        2. - (BOOL) download:(NSString *)paramURLAsString
        3. urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds
        4. updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache{
        5. BOOL result = NO;
        6. if (self.cacheDictionary == nil ||
        7. [paramURLAsString length] == 0){
        8. return(NO);
        9. }
        10. paramURLAsString = [paramURLAsString lowercaseString];
        11. //根據url,從字典中獲取緩存項的相關數據
        12. NSMutableDictionary *itemDictionary =
        13. [self.cacheDictionary objectForKey:paramURLAsString];
        14. /* 使用下面這些變量幫助我們理解緩存邏輯 */
        15. //文件是否已經被緩存
        16. BOOL fileHasBeenCached = NO;
        17. //緩存是否過期
        18. BOOL cachedFileHasExpired = NO;
        19. //緩存文件是否存在
        20. BOOL cachedFileExists = NO;
        21. //緩存文件能否被加載
        22. BOOL cachedFileDataCanBeLoaded = NO;
        23. //緩存文件數據
        24. NSData *cachedFileData = nil;
        25. //緩存文件是否完全下載
        26. BOOL cachedFileIsFullyDownloaded = NO;
        27. //緩存文件是否已經下載
        28. BOOL cachedFileIsBeingDownloaded = NO;
        29. //過期時間
        30. NSDate *expiryDate = nil;
        31. //下載結束時間
        32. NSDate *downloadEndDate = nil;
        33. //下載開始時間
        34. NSDate *downloadStartDate = nil;
        35. //本地緩存路徑
        36. NSString *localURL = nil;
        37. //有效時間
        38. NSNumber *expiresInSeconds = nil;
        39. NSDate *now = [NSDate date];
        40. if (itemDictionary != nil){
        41. fileHasBeenCached = YES;
        42. }
        43. //如果文件已經被緩存,則從緩存項相關數據中獲取相關的值
        44. if (fileHasBeenCached == YES){
        45. expiryDate = [itemDictionary
        46. objectForKey:CachedKeyExpiryDate];
        47. downloadEndDate = [itemDictionary
        48. objectForKey:CachedKeyDownloadEndDate];
        49. downloadStartDate = [itemDictionary
        50. objectForKey:CachedKeyDownloadStartDate];
        51. localURL = [itemDictionary
        52. objectForKey:CachedKeyLocalURL];
        53. expiresInSeconds = [itemDictionary
        54. objectForKey:CachedKeyExpiresInSeconds];
        55. //如果下載開始和結束時間不為空,表示文件全部被下載
        56. if (downloadEndDate != nil &&
        57. downloadStartDate != nil){
        58. cachedFileIsFullyDownloaded = YES;
        59. }
        60. /* 如果expiresInSeconds不為空,downloadEndDate為空,表示文件已經正在下載 */
        61. if (expiresInSeconds != nil &&
        62. downloadEndDate == nil){
        63. cachedFileIsBeingDownloaded = YES;
        64. }
        65. /* 判斷緩存是否過期 */
        66. if (expiryDate != nil &&
        67. [now timeIntervalSinceDate:expiryDate] > 0.0){
        68. cachedFileHasExpired = YES;
        69. }
        70. if (cachedFileHasExpired == NO){
        71. /* 如果緩存文件沒有過期,加載緩存文件,並且更新過期時間 */
        72. NSFileManager *fileManager = [[NSFileManager alloc] init];
        73. if ([fileManager fileExistsAtPath:localURL] == YES){
        74. cachedFileExists = YES;
        75. cachedFileData = [NSData dataWithContentsOfFile:localURL];
        76. if (cachedFileData != nil){
        77. cachedFileDataCanBeLoaded = YES;
        78. } /* if (cachedFileData != nil){ */
        79. } /* if ([fileManager fileExistsAtPath:localURL] == YES){ */
        80. [fileManager release];
        81. /* 更新緩存時間 */
        82. if (paramUpdateExpiryDateIfInCache == YES){
        83. NSDate *newExpiryDate =
        84. [NSDate dateWithTimeIntervalSinceNow:
        85. paramURLMustExpireInSeconds];
        86. NSLog(@"Updating the expiry date from %@ to %@.",
        87. expiryDate,
        88. newExpiryDate);
        89. [itemDictionary setObject:newExpiryDate
        90. forKey:CachedKeyExpiryDate];
        91. NSNumber *expires =
        92. [NSNumber numberWithFloat:paramURLMustExpireInSeconds];
        93. [itemDictionary setObject:expires
        94. forKey:CachedKeyExpiresInSeconds];
        95. }
        96. } /* if (cachedFileHasExpired == NO){ */
        97. }
        98. if (cachedFileIsBeingDownloaded == YES){
        99. NSLog(@"這個文件已經正在下載...");
        100. return(YES);
        101. }
        102. if (fileHasBeenCached == YES){
        103. if (cachedFileHasExpired == NO &&
        104. cachedFileExists == YES &&
        105. cachedFileDataCanBeLoaded == YES &&
        106. [cachedFileData length] > 0 &&
        107. cachedFileIsFullyDownloaded == YES){
        108. /* 如果文件有緩存而且沒有過期 */
        109. NSLog(@"文件有緩存而且沒有過期.");
        110. [self.delegate
        111. cachedDownloadManagerSucceeded:self
        112. remoteURL:[NSURL URLWithString:paramURLAsString]
        113. localURL:[NSURL URLWithString:localURL]
        114. aboutToBeReleasedData:cachedFileData
        115. isCachedData:YES];
        116. return(YES);
        117. } else {
        118. /* 如果文件沒有被緩存,獲取緩存失敗 */
        119. NSLog(@"文件沒有緩存.");
        120. [self.cacheDictionary removeObjectForKey:paramURLAsString];
        121. [self saveCacheDictionary];
        122. } /* if (cachedFileHasExpired == NO && */
        123. } /* if (fileHasBeenCached == YES){ */
        124. /* 去下載文件 */
        125. NSNumber *expires =
        126. [NSNumber numberWithFloat:paramURLMustExpireInSeconds];
        127. NSMutableDictionary *newDictionary =
        128. [[[NSMutableDictionary alloc] init] autorelease];
        129. [newDictionary setObject:expires
        130. forKey:CachedKeyExpiresInSeconds];
        131. localURL = [paramURLAsString
        132. stringByAddingPercentEscapesUsingEncoding:
        133. NSUTF8StringEncoding];
        134. localURL = [localURL stringByReplacingOccurrencesOfString:@"://"
        135. withString:@""];
        136. localURL = [localURL stringByReplacingOccurrencesOfString:@"/"
        137. withString:@"{1}quot;];
        138. localURL = [localURL stringByAppendingPathExtension:@"cache"];
        139. NSString *documentsDirectory =
        140. [self documentsDirectoryWithTrailingSlash:NO];
        141. localURL = [documentsDirectory
        142. stringByAppendingPathComponent:localURL];
        143. [newDictionary setObject:localURL
        144. forKey:CachedKeyLocalURL];
        145. [newDictionary setObject:now
        146. forKey:CachedKeyDownloadStartDate];
        147. [self.cacheDictionary setObject:newDictionary
        148. forKey:paramURLAsString];
        149. [self saveCacheDictionary];
        150. CacheItem *item = [[[CacheItem alloc] init] autorelease];
        151. [item setDelegate:self];
        152. [item startDownloadingURL:paramURLAsString];
        153. return(result);
        154. }
        155. 4、下面我們設計緩存項下載成功和失敗的兩個委托方法:

          1. @protocol CacheItemDelegate
          2. //下載成功執行該方法
          3. - (void) cacheItemDelegateSucceeded
          4. :(CacheItem *)paramSender
          5. withRemoteURL:(NSURL *)paramRemoteURL
          6. withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData;
          7. //下載失敗執行該方法
          8. - (void) cacheItemDelegateFailed
          9. :(CacheItem *)paramSender
          10. remoteURL:(NSURL *)paramRemoteURL
          11. withError:(NSError *)paramError;
          12. @end

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

            1. //緩存項的委托方法
            2. - (void) cacheItemDelegateSucceeded:(CacheItem *)paramSender
            3. withRemoteURL:(NSURL *)paramRemoteURL
            4. withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData{
            5. //從緩存字典中獲取該緩存項的相關數據
            6. NSMutableDictionary *dictionary =
            7. [self.cacheDictionary objectForKey:[paramRemoteURL absoluteString]];
            8. //取當前時間
            9. NSDate *now = [NSDate date];
            10. //獲取有效時間
            11. NSNumber *expiresInSeconds = [dictionary
            12. objectForKey:CachedKeyExpiresInSeconds];
            13. //轉換成NSTimeInterval
            14. NSTimeInterval expirySeconds = [expiresInSeconds floatValue];
            15. //修改字典中緩存項的下載結束時間
            16. [dictionary setObject:[NSDate date]
            17. forKey:CachedKeyDownloadEndDate];
            18. //修改字典中緩存項的緩存過期時間
            19. [dictionary setObject:[now dateByAddingTimeInterval:expirySeconds]
            20. forKey:CachedKeyExpiryDate];
            21. //保存緩存字典
            22. [self saveCacheDictionary];
            23. NSString *localURL = [dictionary objectForKey:CachedKeyLocalURL];
            24. /* 將下載的數據保持到磁盤 */
            25. if ([paramAboutToBeReleasedData writeToFile:localURL
            26. atomically:YES] == YES){
            27. NSLog(@"緩存文件到磁盤成功.");
            28. } else{
            29. NSLog(@"緩存文件到磁盤失敗.");
            30. }
            31. //執行緩存管理的委托方法
            32. [self.delegate
            33. cachedDownloadManagerSucceeded:self
            34. remoteURL:paramRemoteURL
            35. localURL:[NSURL URLWithString:localURL]
            36. aboutToBeReleasedData:paramAboutToBeReleasedData
            37. isCachedData:NO];
            38. }

            39. 如果下載失敗我們需要從緩存字典中移除改緩存項:

              1. //緩存項失敗失敗的委托方法
              2. - (void) cacheItemDelegateFailed:(CacheItem *)paramSender
              3. remoteURL:(NSURL *)paramRemoteURL
              4. withError:(NSError *)paramError{
              5. /* 從緩存字典中移除緩存項,並發送一個委托 */
              6. if (self.delegate != nil){
              7. NSMutableDictionary *dictionary =
              8. [self.cacheDictionary
              9. objectForKey:[paramRemoteURL absoluteString]];
              10. NSString *localURL = [dictionary
              11. objectForKey:CachedKeyLocalURL];
              12. [self.delegate
              13. cachedDownloadManagerFailed:self
              14. remoteURL:paramRemoteURL
              15. localURL:[NSURL URLWithString:localURL]
              16. withError:paramError];
              17. }
              18. [self.cacheDictionary
              19. removeObjectForKey:[paramRemoteURL absoluteString]];
              20. }
              21. 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. cacheDictionary = [dictionary mutableCopy];
                19. [dictionary release];
                20. //移除沒有下載完成的緩存數據
                21. [self removeCorruptedCachedItems];
                22. } else {
                23. //創建一個新的緩存字典
                24. NSMutableDictionary *dictionary =
                25. [[NSMutableDictionary alloc] init];
                26. cacheDictionary = [dictionary mutableCopy];
                27. [dictionary release];
                28. }
                29. 這樣就基本上完成了我們需要的功能,下面看看我們如何使用我們設計的緩存功能。

                  例子場景

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



  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved