iOS預加載Web頁面方案

khan7923 8年前發布 | 7K 次閱讀 iOS開發 移動開發

iOS預加載Web頁面方案

可以預加載多個網址,然后在離線狀態去顯示那幾個網址,看看是不是都完全緩存下來了。

使用方法

在需要開啟預加載的地方創建

self.sCache = [STMURLCache create:^(STMURLCacheMk *mk) {
    mk.whiteListsHost(whiteLists).whiteUserAgent(@"starming");
}];

這里是所有可設置項目,默認設置可以查看 model 的 get 方法

- (STMURLCacheMk *(^)(NSUInteger)) memoryCapacity;   //內存容量

  • (STMURLCacheMk *(^)(NSUInteger)) diskCapacity; //本地存儲容量
  • (STMURLCacheMk *(^)(NSUInteger)) cacheTime; //緩存時間
  • (STMURLCacheMk (^)(NSString )) subDirectory; //子目錄
  • (STMURLCacheMk *(^)(BOOL)) isDownloadMode; //是否啟動下載模式
  • (STMURLCacheMk (^)(NSArray )) whiteListsHost; //域名白名單
  • (STMURLCacheMk (^)(NSString )) whiteUserAgent; //WebView的user-agent白名單

  • (STMURLCacheMk (^)(NSString )) addHostWhiteList; //添加一個域名白名單

  • (STMURLCacheMk (^)(NSString )) addRequestUrlWhiteList; //添加請求白名單

//NSURLProtocol相關設置

  • (STMURLCacheMk *(^)(BOOL)) isUsingURLProtocol; //是否使用NSURLProtocol,默認使用NSURLCache</code></pre>

    也可以隨時更新這些設置項

    [self.sCache update:^(STMURLCacheMk *mk) {
      mk.isDownloadMode(YES);
    }];

    預加載名單可以按照整個 web 頁面請求進行預加載

    [self.sCache preLoadByWebViewWithUrls:@[@"http://www.v2ex.com",@"http://www.github.com"];

    如果需要按照單個資源列表進行預加載可以使用 preLoadByRequestWithUrls 這個方法。

    白名單設置

    對于只希望緩存特定域名或者地址的可以通過白名單進行設置,可以在創建時進行設置或者更新時設置。

    NSString *whiteListStr = @"www.starming.com|www.github.com|www.v2ex.com|www.baidu.com";
    NSMutableArray *whiteLists = [NSMutableArray arrayWithArray:[whiteListStr componentsSeparatedByString:@"|"]];
    self.sCache = [STMURLCache create:^(STMURLCacheMk *mk) {
      mk.whiteListsHost(whiteLists).whiteUserAgent(@"starming");
    }];

    這里的 whiteUserAgent 的設置會設置 webview 的 UserAgent,這樣能夠讓webview以外的網絡請求被過濾掉。

    基本加載緩存實現原理

    創建 STMURLCache 后設置 NSURLCache 的 URLCache ,在 cachedResponseForRequest 方法中獲取 NSURLRequest 判斷白名單,檢驗是否有與之對應的 Cache ,有就使用本地數據返回 NSCachedURLResponse ,沒有就通過網絡獲取數據數據緩存。 STMURLCache 對象釋放時將 NSURLCache 設置為不緩存,表示這次預加載完成不需要再緩存。當緩存空間超出設置大小會將其清空。

    使用 NSURLProtocol 這種原理基本類似。

    白名單實現原理

    創建域名列表設置項 whiteListsHost 和 userAgent 設置項,在創建和更新時對其進行設置。在網絡請求開始通過設置項進行過濾。具體實現如下

    //對于域名白名單的過濾
    if (self.mk.cModel.whiteListsHost.count > 0) {
      id isExist = [self.mk.cModel.whiteListsHost objectForKey:[self hostFromRequest:request]];
      if (!isExist) {
          return nil;
      }
    }
    //User-Agent來過濾
    if (self.mk.cModel.whiteUserAgent.length > 0) {
      NSString *uAgent = [request.allHTTPHeaderFields objectForKey:@"User-Agent"];
      if (uAgent) {
          if (![uAgent hasSuffix:self.mk.cModel.whiteUserAgent]) {
              return nil;
          }
      }
    }

    具體緩存實現

    緩存的實現有兩種,一種是 NSURLCache 另一種是 NSURLProtocolSTMURLCache 同時支持了這兩種,通過 STMURLCacheModel 里的 isUsingURLProtocol 設置項來選擇使用哪個。

    NSURLCache的實現

    沒有緩存的 request 會對其進行請求將獲取數據按照hash地址存兩份于本地,一份是數據,一份記錄時間和類型,時間記錄可以用于判斷失效時間。對于判斷是否有緩存可以根據請求地址對應的文件進行判斷。具體實現如下:

    - (NSCachedURLResponse *)localCacheResponeWithRequest:(NSURLRequest *)request {
      __block NSCachedURLResponse *cachedResponse = nil;
      NSString *filePath = [self filePathFromRequest:request isInfo:NO];
      NSString *otherInfoPath = [self filePathFromRequest:request isInfo:YES];
      NSDate *date = [NSDate date];
      NSFileManager *fm = [NSFileManager defaultManager];
      if ([fm fileExistsAtPath:filePath]) {
          //有緩存文件的情況
          BOOL expire = false;
          NSDictionary *otherInfo = [NSDictionary dictionaryWithContentsOfFile:otherInfoPath];
          if (self.cacheTime > 0) {
              NSInteger createTime = [[otherInfo objectForKey:@"time"] integerValue];
              if (createTime + self.cacheTime < [date timeIntervalSince1970]) {
                  expire = true;
              }
          }
          if (expire == false) {
              //從緩存里讀取數據
              NSData *data = [NSData dataWithContentsOfFile:filePath];
              NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:[otherInfo objectForKey:@"MIMEType"] expectedContentLength:data.length textEncodingName:[otherInfo objectForKey:@"textEncodingName"]];
              NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];
              return cachedResponse;
          } else {
              //cache失效了
              [fm removeItemAtPath:filePath error:nil];      //清除緩存data
              [fm removeItemAtPath:otherInfoPath error:nil]; //清除緩存其它信息
              return nil;
          }
      } else {
          //從網絡讀取
          self.isSavedOnDisk = NO;
          id isExist = [self.responseDic objectForKey:request.URL.absoluteString];
          if (isExist == nil) {
              [self.responseDic setValue:[NSNumber numberWithBool:TRUE] forKey:request.URL.absoluteString];
              NSURLSession *session = [NSURLSession sharedSession];
              NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                  if (error) {
                      cachedResponse = nil;
                  } else {
                      NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%f",[date timeIntervalSince1970]],@"time",response.MIMEType,@"MIMEType",response.textEncodingName,@"textEncodingName", nil];
                      BOOL resultO = [dic writeToFile:otherInfoPath atomically:YES];
                      BOOL result = [data writeToFile:filePath atomically:YES];
                      if (resultO == NO || result == NO) {
                      } else {
                      }
                      cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];
                  }
              }];
              [task resume];
              return cachedResponse;
          }
          return nil;
      }
    }

    NSURLProtocol的實現

    在設置配置項和更新配置項時需要創建一個 STMURLCacheModel 的單例來進行設置和更新配置項給 NSURLProtocol 的實現來使用。通過 isUsingURLProtocol 設置項區分, NSURLProtocol 是通過registerClass方式將protocol實現的進行注冊。

    - (STMURLCache *)configWithMk {
    
      self.mk.cModel.isSavedOnDisk = YES;
    
      if (self.mk.cModel.isUsingURLProtocol) {
          STMURLCacheModel *sModel = [STMURLCacheModel shareInstance];
          sModel.cacheTime = self.mk.cModel.cacheTime;
          sModel.diskCapacity = self.mk.cModel.diskCapacity;
          sModel.diskPath = self.mk.cModel.diskPath;
          sModel.cacheFolder = self.mk.cModel.cacheFolder;
          sModel.subDirectory = self.mk.cModel.subDirectory;
          sModel.whiteUserAgent = self.mk.cModel.whiteUserAgent;
          sModel.whiteListsHost = self.mk.cModel.whiteListsHost;
          [NSURLProtocol registerClass:[STMURLProtocol class]];
      } else {
          [NSURLCache setSharedURLCache:self];
      }
      return self;
    }

    關閉時兩者也是不同的,通過設置項進行區分

    - (void)stop {
      if (self.mk.cModel.isUsingURLProtocol) {
          [NSURLProtocol unregisterClass:[STMURLProtocol class]];
      } else {
          NSURLCache *c = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
          [NSURLCache setSharedURLCache:c];
      }
      [self.mk.cModel checkCapacity];
    }

    白名單處理還有讀取緩存和前者都類似,但是在緩存Data時 NSURLCached 的方案里是通過發起一次新的請求來獲取數據,而 NSURLProtocolNSURLConnection 的 Delegate 里可以獲取到,少了一次網絡的請求,這里需要注意的是在 - (void) connection:(NSURLConnection )connection didReceiveData:(NSData )data 每次從這個回調里獲取的數據不是完整的,要在 - (void) connectionDidFinishLoading:(NSURLConnection *)connection 這個會調里將分段數據拼接成完整的數據保存下來。具體完整的代碼實現可以看 STMURLProtocol 里的代碼實現。

    后記

    通過 map 網絡請求可以緩存請求,也可以 mock 接口請求進行測試。

     

    來自:http://www.cocoachina.com/ios/20161130/18230.html

     

 本文由用戶 khan7923 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!