我是如何一步一步實現網頁離線緩存的?

来源:http://www.cnblogs.com/zhanggui/archive/2017/11/28/7909355.html
-Advertisement-
Play Games

一個Hybrid APP,如何做離線緩存策略?也可以簡單來說,你的APP只是一個殼,裡面真正載入的內容是H5,如果優化載入內容的速度? ...


問題

一個Hybrid APP,如何做離線緩存策略?也可以簡單來說,你的APP只是一個殼,裡面真正載入的內容是H5,如果優化載入內容的速度?

先瞭解一下NSURLProtocol

從字面意思看它是一個協議,但是它其實是一個類,而且繼承自NSObject。它的作用是處理特定URL協議的載入。它本身是一個抽象類,提供了使用特性URL方案處理URL的基礎結構。你可以自己創建NSURLProtocol的子類,來讓自己的應用支持自定義的協議或者URL方案。
應用程式永遠不需要直接實例化一個NSURLProtocol子類。當一個下載開始的時候,系統創建一個合適的protoco對象來響應URL請求。你要做的就是自己定義一個你自己的protocol,然後在APP啟動的時候調用registerClass:,讓系統知道你的協議。
這裡需要註意:你不能在watchOS 2以及更高版本中自定義URL scheme和協議。

為了支持特定的自定義請求,你最好定義NSURLRequest 或者NSMutableURLRequest。讓自定義的這些對象來實現請求,這裡需要使用NSURLProtocol的propertyForKey:inRequest:和setProperty:forKey:inRequest,然後你可以自定義NSURLResponse類來模擬返回信息。
接下來就開始對UIWebView進行離線緩存處理。

UIWebView的離線緩存處理

首先,我們需要自定義一個NSURLProtocol的子類,並且在AppDelegate.m的

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [NSURLProtocol registerClass:[ZGURLProtocol class]];
    return YES;
}

註冊。接下來的所有操作就都是在我們自定義的ZGURLProtocol中操作了。先看一下registerClass的作用:
嘗試註冊一個NSURLProtocol的子類,使其對URL Loading System可見。這裡的URL Loading System就是一組類和協議,允許你的應用程式訪問由URL產生的內容,比如請求、接收內容和Cache等。當URL Load System開始載入一個請求的時候,每個註冊的協議類都被依次去調用,以確定是否可以用指定的請求去初始化它。首先被調用的方法是:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request;

在該方法裡面進行緩存過濾,比如你想只緩存js,那麼判斷request的path的尾碼,如果是js,就返回YES,否則返回NO。
如果返回YES,那麼就相當於該請求被自定義的URLProtocol來處理,這裡不能保證所有的註冊的NSURLProtocol都能被處理到。如果你定義了多個NSProtocol子類,這些子類將會以相反的順序調用。也就是說如果你是這樣寫的:

 [NSURLProtocol registerClass:[ZGURLProtocol class]];
 [NSURLProtocol registerClass:[ZProtocol class]];

那麼最先執行的是ZProtocol,如果參initWithRequest:返回的為YES,則請求由ZProtocol進行處理,且不會再走ZGURLProtocol。如果ZProtocol的initWithRequest:返回的為NO,則請求繼續向下傳遞由其他的NSURLProtocol子類處理。
一旦返回YES,那麼請求將會由自己寫的子類處理,首先會調用:

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

這個是一個抽象的方法,子類必須對其實現。通常情況下,我們一般都是直接返回request,但是這裡你也可以直接修改此request,包括header,hosts等。可以對指定request進行重定向操作。
在這裡,我們只是將現有的request進行返回即可。
緊接著,便會開始請求:

- (void)startLoading;

該方法的作用就是開始請求protocol指定的請求。該方法也是protocol子類必須實現的方法。在這裡所做的操作就是:
先判斷是否有緩存數據,如果有,則自己創建NSURLResponse,然後將緩存數據放入,併進行client的一些操作,然後返回;如果沒有緩存數據,則新建一個NSURLConnection,然後發送請求。
先說一下有緩存的情況下:

if (model.data && model.MIMEType) {
        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:model.MIMEType expectedContentLength:model.data.length textEncodingName:nil];
        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
        [self.client URLProtocol:self didLoadData:model.data];
        [self.client URLProtocolDidFinishLoading:self];
        return;
    }

(model是緩存數據)有緩存的情況下,直接使用緩存的數據和MIME類型,然後構建NSURLResponse,然後通過協議client調用代理方法。這裡的client是一個protocol,如下:

@protocol NSURLProtocolClient <NSObject>

- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;

- (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;

- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;

- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;

- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;

- (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error;

- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

- (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

@end

該協議提供了NSURLProtocol子類與URL Loading System進行溝通的介面。一個APP一定不要去實現這個協議。有緩存的情況下調用回調方法,然後進行處理。
在沒有緩存的情況下:
實例化一個connection,然後發起請求。在我們收到response的時候:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    self.responseData = [[NSMutableData alloc] init];
    self.responseMIMEType = response.MIMEType;
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

緊接著就是接收數據:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.responseData appendData:data];
    [self.client URLProtocol:self didLoadData:data];
}

接收完數據之後便調用了:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    ZGCacheModel *model = [ZGCacheModel new];
    model.data = self.responseData;
    model.MIMEType = self.responseMIMEType;
    [self setMiType:model.MIMEType withKey:[self.request.URL path]];//userdefault存儲MIMEtype
    
    
    [[ZGUIWebViewCache sharedWebViewCache] setCacheWithKey:self.request.URL.absoluteString value:model];
  
    [self.client URLProtocolDidFinishLoading:self];
}

這個方法是結束家在之後的調用,我們需要在這裡將請求過來的數據進行緩存。這樣我們本地就有了指定URL的返回數據。
這裡還有一個重要的東西沒有介紹,那就是

[NSURLProtocol propertyForKey:ZGURLProtocolKey inRequest:request]
[NSURLProtocol setProperty:@YES forKey:ZGURLProtocolKey inRequest:mutableRequest];

這裡的

+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;

作用是在指定的請求中設置與特定的鍵值相關聯。防止多次調用一個request。
這樣,我們就完成了UIWebView的離線緩存。在這裡我封裝了一個ZGUIWebViewCache。感興趣的可以看一下。

WKWebView的離線緩存處理

WKWebView離線緩存和UIWebView緩存類似,只不過使用WKWebView除了一開始調用一下NSURLProtocol的canInitWithRequest:方法之後,之後的請求似乎就和NSURLProtocol完全無關了,網上都說WKWebView的請求是在獨立的進程里,所以不走NSURLProtocol。這裡是通過NSURLProtocol+WKWebView類進行處理的,詳情可參見:ZGWKWebViewCache
剩下的處理過程就和UIWebView緩存處理類似了。
以上便是對網頁離線緩存的實現。
如有問題,歡迎留言溝通!

參考

1.讓 WKWebView 支持 NSURLProtocol


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 有時候我們需要頁面滾動條滾動到某一固定的位置,一般使用Window scrollTo() 方法。 語法就是:scrollTo(xpos,ypos) xpos:必需。要在視窗文檔顯示區左上角顯示的文檔的 x 坐標。 ypos:必需。要在視窗文檔顯示區左上角顯示的文檔的 y 坐標。 例如滾動內容的坐標位 ...
  • DOM.map(callback(index,domElement)); 對匹配元素執行函數對象。 返回值是 jQuery 封裝的數組,使用 get() 來處理返回的對象以得到基礎的數組。 返回數據類型是object。 ...
  • 1. 下載Charles Proxy v4.2.1 版本,百度雲盤下載或去官網下載 2. 安裝後先打開Charles一次(Windows版可以忽略此步驟) 3. 在這個網站(http://charles.iiilab.com/)下載破解文件 charles.jar 4. 替換掉原文件夾里的charl ...
  • 本章目標 一、瞭解Android 二、熟悉Android開發環境 三、可以搭建運行簡單Android項目 四、掌握Android應用開發流程 一、什麼是Android? Android是一個基於Linux的自由及開放源代碼的操作系統 1、利用Linux作為操作系統的內核 2、存儲管理、設備管理、文件 ...
  • 問題 ios真機中Text組件出現多餘邊框(模擬器不會出現,真機會出現該問題)。 原因 在ios啟動頁設置中,預設的尺寸要求與設置中圖片尺寸不符合導致屏幕精度計算出現問題( 啟動屏解析度錯誤設置會導致手機解析度錯誤解析 )。 解決 按照預設尺寸要求設置對應尺寸的啟動頁圖片,確保一一對應。 ...
  • 平時都是用AS敲命令獲取簽名信息。。。還沒有在代碼中獲取過簽名~ 也算是老編程了,沒做過這個稍微有點尷尬。。。本著有好輪子就用的原則,網上找了幾篇博客,這塊內容已經很完善了,我也沒什麼可以優化的。。。 主要參(zhao)考(chao)了http://blog.csdn.net/hcwfc/artic ...
  • 開發中顏色的使用也是非常頻繁的,這裡推薦一個dsxNiubility大牛寫的顏色庫:Wonderful;它的好用就是很清楚的把每個常用的顏色進行了由淺到深的分層,讓我們使用時可以根據自己對顏色的深淺直接取顏色值;其中的功能我比較常用的就是顏色、跑馬燈和顏色漸變,裡面還有其他的功能,感興趣的可以去看下 ...
  • UIButton的預設佈局是:title在右,image在左; 很多時候我們需要的是title在左邊,或者title在下麵,這時就需要調整UIButton的TitleLabel和ImageView的位置了,查了很多資料,要麼零零散散的介紹,要麼就是特別複雜的實現;經過一段時間的學習,在這裡總結一下實 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...