<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          干貨:探秘WKWebView

          共 17123字,需瀏覽 35分鐘

           ·

          2021-11-01 15:13

          ?

          本文字?jǐn)?shù):6540

          預(yù)計(jì)閱讀時(shí)間:18分鐘

          概述

          之前主要使用UIWebView進(jìn)行頁面的加載,但是UIWebView存在很多問題,在2020年已經(jīng)被蘋果正式拋棄。所以本篇文章主要講解WKWebView,WKWebViewiOS8開始支持,現(xiàn)在大多數(shù)App應(yīng)該都不支持iOS7了。

          UIWebView存在兩個(gè)問題,一個(gè)是內(nèi)存消耗比較大,另一個(gè)是性能很差。WKWebView相對于UIWebView來說,性能要比UIWebView性能要好太多,刷新率能達(dá)到60FPS。內(nèi)存占用也比UIWebView要小。

          WKWebView是一個(gè)多進(jìn)程組件,NetworkUI Render都在獨(dú)立的進(jìn)程中完成。

          由于WKWebViewApp不在同一個(gè)進(jìn)程,如果WKWebView進(jìn)程崩潰并不會(huì)導(dǎo)致應(yīng)用崩潰,僅僅是頁面白屏等異常。頁面的載入、渲染等消耗內(nèi)存和性能的操作,都在WKWebView的進(jìn)程中處理,處理后再將結(jié)果交給App進(jìn)程用于顯示,所以App進(jìn)程的性能消耗會(huì)小很多。

          網(wǎng)頁加載流程

          1. 通過域名的方式請求服務(wù)器,請求前瀏覽器會(huì)做一個(gè)DNS解析,并將IP地址返回給瀏覽器。
          2. 瀏覽器使用IP地址請求服務(wù)器,并且開始握手過程。TCP是三次握手,如果使用https則還需要進(jìn)行TLS的握手,握手后根據(jù)協(xié)議字段選擇是否保持連接。
          3. 握手完成后,瀏覽器向服務(wù)端發(fā)送請求,獲取html文件。
          4. 服務(wù)器解析請求,并由CDN服務(wù)器返回對應(yīng)的資源文件。
          5. 瀏覽器收到服務(wù)器返回的html文件,交由html解析器進(jìn)行解析。
          6. 解析html由上到下進(jìn)行解析xml標(biāo)簽,過程中如果遇到css或資源文件,都會(huì)進(jìn)行異步加載,遇到js則會(huì)掛起當(dāng)前html解析任務(wù),請求js并返回后繼續(xù)解析。因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">js文件可能會(huì)對DOM樹進(jìn)行修改。
          7. 解析完html,并執(zhí)行完js代碼,形成最終的DOM樹。通過DOM配合css文件找出每個(gè)節(jié)點(diǎn)的最終展示樣式,并交由瀏覽器進(jìn)行渲染展示
          8. 結(jié)束鏈接。

          代理方法

          WKWebViewUIWebView的代理方法發(fā)生了一些改變,WKWebView的流程更加細(xì)化了。例如之前UI結(jié)束請求后,會(huì)立刻渲染到webView上。而WKWebView則會(huì)在渲染到屏幕之前,會(huì)回調(diào)一個(gè)代理方法,代理方法決定是否渲染到屏幕上。這樣就可以對請求下來的數(shù)據(jù)做一次校驗(yàn),防止數(shù)據(jù)被更改,或驗(yàn)證視圖是否允許被顯示到屏幕上。

          除此之外,WKWebView相對于UIWebView還多了一些定制化操作。

          1. 重定向的回調(diào),可以在請求重定向時(shí)獲取到這次操作。
          2. 當(dāng)WKWebView進(jìn)程異常退出時(shí),可以通過回調(diào)獲取。
          3. 自定義處理證書。
          4. 更深層的UI定制操作,將alertUI操作交給原生層面處理,而UI方案UIAlertView是直接webView顯示的。

          WKUIDelegate

          WKWebView將很多UI的顯示都交給原生層面去處理,例如彈窗或者輸入框的顯示。這樣如果項(xiàng)目里有統(tǒng)一定義的彈窗,就可以直接調(diào)用自定義彈窗,而不是只能展示系統(tǒng)彈窗。

          WKWebView中,系統(tǒng)將彈窗的顯示交由客戶端來控制??蛻舳丝梢酝ㄟ^下面的回調(diào)方法獲取到彈窗的顯示信息,并由客戶端來調(diào)起UIAlertController來展示。參數(shù)中有一個(gè)completionHandler的回調(diào)block,需要客戶端一定要調(diào)用,如果不調(diào)用則會(huì)發(fā)生崩潰。

          -?(void)webView:(WKWebView?*)webView?runJavaScriptAlertPanelWithMessage:(NSString?*)message?initiatedByFrame:(WKFrameInfo?*)frame?completionHandler:(void?(^)(void))completionHandler;

          有時(shí)候H5會(huì)要求用戶進(jìn)行一些輸入,例如用戶名密碼之類的??蛻舳丝梢酝ㄟ^下面的方法獲取到輸入框事件,并由客戶端展示輸入框,用戶輸入完成后將結(jié)果回調(diào)給completionHandler中。

          -?(void)webView:(WKWebView?*)webView?runJavaScriptTextInputPanelWithPrompt:(NSString?*)prompt?defaultText:(nullable?NSString?*)defaultText?initiatedByFrame:(WKFrameInfo?*)frame?completionHandler:(void?(^)(NSString?*?_Nullable?result))completionHandler;

          WKNavigationDelegate

          關(guān)于加載流程相關(guān)的方法,都被抽象到WKNavigationDelegate中,這里挑幾個(gè)比較常用的方法講一下。

          下面的方法,通過decisionHandler回調(diào)中返回一個(gè)枚舉類型的參數(shù),表示是否允許頁面加載。這里可以對域名進(jìn)行判斷,如果是站外域名,則可以提示用戶是否進(jìn)行跳轉(zhuǎn)。如果是跳轉(zhuǎn)其他App或商店的URL,則可以通過openURL進(jìn)行跳轉(zhuǎn),并將這次請求攔截。包括cookie的處理也在此方法中完成,后面會(huì)詳細(xì)講到cookie的處理。

          除此之外,很多頁面顯示前的邏輯處理,也在此方法中完成。但需要注意的是,方法中不要做過多的耗時(shí)處理,會(huì)影響頁面加載速度。

          -?(void)webView:(WKWebView?*)webView?decidePolicyForNavigationAction:(WKNavigationAction?*)navigationAction?decisionHandler:(void?(^)(WKNavigationActionPolicy))decisionHandler;

          開始加載頁面,并請求服務(wù)器。

          -?(void)webView:(WKWebView?*)webView?didStartProvisionalNavigation:(null_unspecified?WKNavigation?*)navigation;

          當(dāng)頁面加載失敗的時(shí)候,會(huì)回調(diào)此方法,包括timeout等錯(cuò)誤。在這個(gè)頁面可以展示錯(cuò)誤頁面,清空進(jìn)度條,重置網(wǎng)絡(luò)指示器等操作。需要注意的是,調(diào)用goBack時(shí)也會(huì)執(zhí)行此方法,可以通過error的狀態(tài)判斷是否NSURLErrorCancelled來過濾掉。

          -?(void)webView:(WKWebView?*)webView?didFailProvisionalNavigation:(WKNavigation?*)navigation?withError:(NSError?*)error;

          頁面加載及渲染完成,會(huì)調(diào)用此方法,調(diào)用此方法時(shí)H5dom已經(jīng)解析并渲染完成,展示在屏幕上。所以在此方法中可以進(jìn)行一些加載完成的操作,例如移除進(jìn)度條,重置網(wǎng)絡(luò)指示器等。

          -?(void)webView:(WKWebView?*)webView?didFinishNavigation:(null_unspecified?WKNavigation?*)navigation;

          WKUserContentController

          回調(diào)

          WKWebView將和js的交互都由WKUserContentController類來處理,后面統(tǒng)稱為userContent

          如果需要接收并處理js的調(diào)用,通過調(diào)用addScriptMessageHandler:name:方法,并傳入一個(gè)實(shí)現(xiàn)了WKScriptMessageHandler協(xié)議的對象,即可接收js的回調(diào),由于userContent會(huì)強(qiáng)引用傳入的對象,所以應(yīng)該是新創(chuàng)建一個(gè)對象,而不是self。注冊對象時(shí),后面的name就是js調(diào)用的函數(shù)名。

          WKUserContentController?*userContent?=?[[WKUserContentController?alloc]?init];
          [userContent?addScriptMessageHandler:[[WKWeakScriptMessageDelegate?alloc]?initWithDelegate:self]?name:@"clientCallback"];

          dealloc中應(yīng)該通過下面的方法,移除對指定name的處理。

          [userContent?removeScriptMessageHandlerForName:@"clientCallback"];

          H5通過下面的代碼即可對客戶端發(fā)起調(diào)用,調(diào)用是通過postMessage函數(shù)傳一個(gè)json串過來,需要加上轉(zhuǎn)移字符??蛻舳私邮盏秸{(diào)用后,根據(jù)回調(diào)方法傳入的WKScriptMessage對象,獲取到body字典,解析傳入的參數(shù)即可。

          window.webkit.messageHandlers.clientCallback.postMessage("{\"funName\":\"getMobileCode\",\"value\":\"srggshqisslfkj\"}");

          調(diào)用

          原生調(diào)用H5的方法也是一樣,創(chuàng)建一個(gè)WKUserScript對象,并將js代碼當(dāng)做參數(shù)傳入。除了調(diào)用js代碼,也可以通過此方法注入代碼改變頁面dom,但是這樣代碼量較大,不建議這么做。

          WKUserScript?*wkcookieScript?=?[[WKUserScript?alloc]?initWithSource:self.javaScriptString
          ??????????????????????????????????????????????????????????injectionTime:WKUserScriptInjectionTimeAtDocumentStart
          ???????????????????????????????????????????????????????forMainFrameOnly:NO];
          [webView.configuration.userContentController?addUserScript:wkcookieScript];

          WKUserScript vs evaluateJavaScript

          WKWebView對于執(zhí)行js代碼提供了兩種方式,通過userContent添加一個(gè)WKUserScript對象的方式,以及通過webViewevaluateJavaScript:completionHandler:方式,注入js代碼。

          NSString?*removeChildNode?=?@""
          "var?header?=?document.getElementsByTagName:('header')[0];"
          "header.parentNote.removeChild(header);"
          [self.webView?evaluateJavaScript:removeChildNode?completionHandler:nil];

          首先要說明的是,這兩種方式都可以注入js代碼,但是其內(nèi)部的實(shí)現(xiàn)方式我沒有深入研究,WebKit內(nèi)核是開源的,有興趣的同學(xué)可以看看。但是這兩種方式還是有一些功能上的區(qū)別的,可以根據(jù)具體業(yè)務(wù)場景去選擇對應(yīng)的API。

          先說說evaluateJavaScript:completionHandler:的方式,這種方式一般是在頁面展示完成后執(zhí)行的操作,用來調(diào)用js的函數(shù)并獲取返回值非常方便。當(dāng)然也可以用來注入一段js代碼,但需要自己控制注入時(shí)機(jī)。

          WKUserScript則可以控制注入時(shí)機(jī),可以針對document是否加載完選擇注入js。以及被注入的js是在當(dāng)前頁面有效,還是包括其子頁面也有效。相對于evaluateJavaScript:方法,此方法不能獲得js執(zhí)行后的返回值,所以兩個(gè)方法在功能上還是有區(qū)別的。

          容器設(shè)計(jì)

          設(shè)計(jì)思路

          項(xiàng)目中一般不會(huì)直接使用WKWebView,而是通過對其進(jìn)行一層包裝,成為一個(gè)WKWebViewController交給業(yè)務(wù)層使用。設(shè)計(jì)webViewVC時(shí)應(yīng)該遵循簡單靈活的思想去設(shè)計(jì),自身只提供展示功能,不涉及任何業(yè)務(wù)邏輯。對外提供展示導(dǎo)航欄、設(shè)置標(biāo)題、進(jìn)度條等功能,都可以通過WKWebViewConfiguration賦值并在WKWebViewController實(shí)例化的時(shí)候傳入。

          對調(diào)用方提供js交互、webView生命周期、加載錯(cuò)誤等回調(diào),外接通過對應(yīng)的回調(diào)進(jìn)行處理。這些回調(diào)都是可選的,不實(shí)現(xiàn)對webView加載也沒有影響。下面是實(shí)例代碼,也可以把不同類型的回調(diào)拆分定義不同的代理。

          @protocol?WKWebViewControllerDelegate?<NSObject>
          @optional
          -?(void)webViewDidStartLoad:(WKWebViewController?*)webViewVC;
          -?(void)webViewDidFinishLoad:(WKWebViewController?*)webViewVC;
          -?(void)webView:(WKWebViewController?*)webViewVC?didFailLoadWithError:(NSError?*)error;
          -?(void)webview:(WKWebViewController?*)webViewVC?closeWeb:(NSString?*)info;
          -?(void)webview:(WKWebViewController?*)webViewVC?login:(NSDictionary?*)info;
          -?(void)webview:(WKWebViewController?*)webViewVC?jsCallbackParams:(NSDictionary?*)params;
          @end

          此外,WKWebViewController還應(yīng)該負(fù)責(zé)處理公共參數(shù),并且可以基于公共參數(shù)進(jìn)行擴(kuò)展。這里我們定義了一個(gè)方法,可以指定基礎(chǔ)參數(shù)的位置,是通過URL拼接、header、js注入等方式添加,這個(gè)枚舉是多選的,也就是可以在多個(gè)位置進(jìn)行注入。除了基礎(chǔ)參數(shù),還可以額外添加自定義參數(shù),也會(huì)添加到指定的位置。

          -?(void)injectionParamsType:(SVParamsType)type?additionalParams:(NSDictionary?*)additionalParams;

          復(fù)用池

          WKWebView第一次初始化的時(shí)候,會(huì)先啟動(dòng)webKit內(nèi)核,并且有一些初始化操作,這個(gè)操作是非常消耗性能的。所以,復(fù)用池設(shè)計(jì)的第一步,是在App啟動(dòng)的時(shí)候,初始化一個(gè)全局的WKWebView。

          并且,創(chuàng)建兩個(gè)池子,創(chuàng)建visiblePool存放正在使用的,創(chuàng)建reusablePool存放空閑狀態(tài)的。并且,在頁面退出時(shí),從visiblePool放入reusablePool的同時(shí),應(yīng)該將頁面進(jìn)行回收,清除頁面上的數(shù)據(jù)。

          當(dāng)需要初始化一個(gè)webView容器時(shí),從reusablePool中取出一個(gè)容器,并且放入到visiblePool中。通過復(fù)用池的實(shí)現(xiàn),可以減少從初始化一個(gè)webView容器,到頁面展示出來的時(shí)間。

          WKProcessPool

          WKWebView中定義了processPool屬性,可以指定對應(yīng)的進(jìn)程池對象。每個(gè)webView都有自己的內(nèi)容進(jìn)程,如果不指定則默認(rèn)是一個(gè)新的內(nèi)容進(jìn)程。內(nèi)容進(jìn)程中包括一些本地cookie、資源之類的,如果不在一個(gè)內(nèi)容進(jìn)程中,則不能共享這些數(shù)據(jù)。

          可以創(chuàng)建一個(gè)公共的WKProcessPool,是一個(gè)單例對象。所有webView創(chuàng)建的時(shí)候,都使用同一個(gè)內(nèi)容進(jìn)程,即可實(shí)現(xiàn)資源共享。

          UserAgent

          User-Agent是在http協(xié)議中的一個(gè)請求頭字段,用來告知服務(wù)器一些信息的,User-Agent中包含了很多字段,例如系統(tǒng)版本、瀏覽器內(nèi)核版本、網(wǎng)絡(luò)環(huán)境等。這個(gè)字段可以直接用系統(tǒng)提供的,也可以在原有User-Agent的基礎(chǔ)上添加其他字段。

          例如下面是從系統(tǒng)的webView中獲取到的User-Agent。

          Mozilla/5.0?(iPhone;?CPU?iPhone?OS?10_3_2?like?Mac?OS?X)?AppleWebKit/603.2.4?(KHTML,?like?Gecko)?Mobile/14F89

          iOS9之后提供了customUserAgent屬性,直接為WKWebView設(shè)置User-Agent,而iOS9之前需要通過js寫入的方式對H5注入User-Agent。

          通信協(xié)議

          一個(gè)設(shè)計(jì)的比較好的WebView容器,應(yīng)該具備很好的相互通信功能,并且靈活具有擴(kuò)展性。H5和客戶端的通信主要有以下幾種場景。

          • js調(diào)用客戶端,以及js調(diào)用客戶端后獲取客戶端的callback回調(diào)及參數(shù)。
          • 客戶端調(diào)用js,以及調(diào)用js后的callback回調(diào)及參數(shù)。
          • 客戶端主動(dòng)通知H5,客戶端的一些生命周期變化。例如進(jìn)入鎖屏和進(jìn)入前臺等系統(tǒng)生命周期。

          js調(diào)用客戶端為例,有兩個(gè)緯度的調(diào)用。可以通過URLRouter的方式直接調(diào)用某個(gè)模塊,這種調(diào)用方式遵循客戶端的URL定義即可調(diào)起,并且支持傳參。還可以通過userContentController的方式,進(jìn)行頁面級的調(diào)用,例如關(guān)閉webView、調(diào)起登錄功能等,也就是通過js調(diào)用客戶端的某個(gè)功能,這種方式需要客戶端提供對應(yīng)的處理代碼。

          二者之間相互調(diào)用,盡量避免高頻調(diào)用,而且一般也不會(huì)有高頻調(diào)用的需求。但如果發(fā)生相同功能高頻調(diào)用,則需要設(shè)置一個(gè)actionID來區(qū)分不同的調(diào)用,以保證發(fā)生回調(diào)時(shí)可以正常被區(qū)分。

          callback的回調(diào)方法也可以通過參數(shù)傳遞過來,這種方式靈活性比較強(qiáng),如果固定寫死會(huì)有版本限制,較早版本的客戶端可能并不支持這個(gè)回調(diào)。

          處理回調(diào)

          webView的回調(diào)除了基礎(chǔ)的調(diào)用,例如refresh刷新當(dāng)前頁面、close關(guān)閉當(dāng)前頁面等,直接由對應(yīng)的功能類來處理調(diào)用,其他的時(shí)間應(yīng)該交給外界處理。

          這里的設(shè)計(jì)方案并不是一個(gè)事件對應(yīng)一個(gè)回調(diào)方法,然后外界遵循代理并實(shí)現(xiàn)多個(gè)代理方法的方式來實(shí)現(xiàn)。而是將每次回調(diào)事件都封裝成一個(gè)對象,直接將這個(gè)對象回調(diào)給外界處理,這樣靈活性更強(qiáng)一些,而且外界獲取的信息也更多。事件模型的定義可以參考下面的。

          @interface?WKWebViewCallbackModel?:?NSObject
          @property(nonatomic,?strong)?WKWebViewController?*webViewVC;
          @property(nonatomic,?strong)?WKCallType?*type;
          @property(nonatomic,?copy)?NSDictionary?*parameters;
          @property(nonatomic,?copy)?NSString?*callbackID;
          @property(nonatomic,?copy)?NSString?*callbackFunction;
          @end

          持久化

          目前H5頁面的持久化方案,主要是WebKit自帶的localStorageCookie,但是Cookie并不是用來做持久化操作的,所以也不應(yīng)該給H5用來做持久化。如果想更穩(wěn)定的進(jìn)行持久化,可以考慮提供一個(gè)js bridgeCRUD接口,讓H5可以用來存儲和查詢數(shù)據(jù)。

          持久化方案就采取和客戶端一致的方案,給H5單獨(dú)建一張數(shù)據(jù)表即可。

          緩存機(jī)制

          緩存規(guī)則

          前端瀏覽器包括WKWebView在內(nèi),為了保證快速打開頁面,減少用戶流量消耗,都會(huì)對資源進(jìn)行緩存。這個(gè)緩存規(guī)則在WKWebView中也可以指定,如果我們?yōu)榱吮WC每次的資源文件都是最新的,也可以選擇不使用緩存,但我們一般不這么做。

          • NSURLRequestUseProtocolCachePolicy = 0,默認(rèn)緩存策略,和Safari內(nèi)核的緩存表現(xiàn)一樣。
          • NSURLRequestReloadIgnoringLocalCacheData = 1, 忽略本地緩存,直接從服務(wù)器獲取數(shù)據(jù)。
          • NSURLRequestReturnCacheDataElseLoad = 2, 本地有緩存則使用緩存,否則加載服務(wù)端數(shù)據(jù)。這種策略不會(huì)驗(yàn)證緩存是否過期。
          • NSURLRequestReturnCacheDataDontLoad = 3, 只從本地獲取,并且不判斷有效性和是否改變,本地沒有不會(huì)請求服務(wù)器數(shù)據(jù),請求會(huì)失敗。
          • NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, 忽略本地以及路由過程中的緩存,從服務(wù)器獲取最新數(shù)據(jù)。
          • NSURLRequestReloadRevalidatingCacheData = 5, 從服務(wù)端驗(yàn)證緩存是否可用,本地不可用則請求服務(wù)端數(shù)據(jù)。
          • NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,


          根據(jù)蘋果默認(rèn)的緩存策略,會(huì)進(jìn)行三步檢查。

          1. 緩存是否存在。
          2. 驗(yàn)證緩存是否過期。
          3. 緩存是否發(fā)生改變。

          緩存文件

          iOS9蘋果提供了緩存管理類WKWebsiteDataStore,通過此類可以對磁盤上,指定類型的緩存文件進(jìn)行查詢和刪除。因?yàn)楝F(xiàn)在很多App都從iOS9開始支持,所以非常推薦此API來管理本地緩存,以及cookie。本地的文件緩存類型定義為以下幾種,常用的主要是cookiediskCache、memoryCache這些。

          • WKWebsiteDataTypeFetchCache,磁盤中的緩存,根據(jù)源碼可以看出,類型是DOMCache
          • WKWebsiteDataTypeDiskCache,本地磁盤緩存,和fetchCache的實(shí)現(xiàn)不同,是所有的緩存數(shù)據(jù)
          • WKWebsiteDataTypeMemoryCache,本地內(nèi)存緩存
          • WKWebsiteDataTypeOfflineWebApplicationCache,離線web應(yīng)用程序緩存
          • WKWebsiteDataTypeCookiescookie緩存
          • WKWebsiteDataTypeSessionStorage,html會(huì)話存儲
          • WKWebsiteDataTypeLocalStoragehtml本地?cái)?shù)據(jù)緩存
          • WKWebsiteDataTypeWebSQLDatabases,WebSQL數(shù)據(jù)庫數(shù)據(jù)
          • WKWebsiteDataTypeIndexedDBDatabases,數(shù)據(jù)庫索引
          • WKWebsiteDataTypeServiceWorkerRegistrations,服務(wù)器注冊數(shù)據(jù)

          通過下面的方法可以獲取本地所有的緩存文件類型,返回的集合字符串,就是上面定義的類型。

          +?(NSSet<NSString?*>?*)allWebsiteDataTypes;

          可以指定刪除某個(gè)時(shí)間段內(nèi),指定類型的數(shù)據(jù),刪除后會(huì)回調(diào)block。

          -?(void)removeDataOfTypes:(NSSet<NSString?*>?*)dataTypes?modifiedSince:(NSDate?*)date?completionHandler:(void?(^)(void))completionHandler;

          系統(tǒng)還提供了定制化更強(qiáng)的方法,通過fetchDataRecordsOfTypes:方法獲取指定類型的所有WKWebsiteDataRecord對象,此對象包含域名和類型兩個(gè)參數(shù)??梢愿鶕?jù)域名和類型進(jìn)行判斷,隨后調(diào)用removeDataOfTypes:方法傳入需要?jiǎng)h除的對象,對指定域名下的數(shù)據(jù)進(jìn)行刪除。

          //?獲取
          -?(void)fetchDataRecordsOfTypes:(NSSet<NSString?*>?*)dataTypes?completionHandler:(void?(^)(NSArray<WKWebsiteDataRecord?*>?*))completionHandler;
          //?刪除
          -?(void)removeDataOfTypes:(NSSet<NSString?*>?*)dataTypes?forDataRecords:(NSArray<WKWebsiteDataRecord?*>?*)dataRecords?completionHandler:(void?(^)(void))completionHandler;

          http緩存策略

          客戶端和H5在打交道的時(shí)候,經(jīng)常會(huì)出現(xiàn)頁面緩存的問題,H5的開發(fā)同學(xué)就經(jīng)常說“你清一下緩存試試”,實(shí)際上發(fā)生這個(gè)問題的原因,在于H5的緩存管理策略有問題。這里就講一下H5的緩存管理策略。

          H5的緩存管理其實(shí)就是利用http協(xié)議的字段進(jìn)行管理的,比較常用的是Cache-ControlLast-Modified搭配使用的方式。

          • Cache-Control:文件緩存有效時(shí)長,例如請求文件后服務(wù)器響應(yīng)頭返回Cache-Control:max-age=600,則表示文件有效時(shí)長600秒。所以此文件在有效時(shí)長內(nèi),都不會(huì)發(fā)出網(wǎng)絡(luò)請求,直到過期為止。
          • Last-Modified:請求文件后服務(wù)器響應(yīng)頭中返回的,表示文件的最新更新時(shí)間。如果Cache-Control過期后,則會(huì)請求服務(wù)器并將這個(gè)時(shí)間放在請求頭的If-Modified-Since字段中,服務(wù)器收到請求后會(huì)進(jìn)行時(shí)間對比,如果時(shí)間沒有發(fā)生改變則返回304,否則返回新的文件和響應(yīng)頭字段,并返回200

          Cache-Controlhttp1.1出來的,表示文件的相對有效時(shí)長,在此之前還有Expires字段,表示文件的絕對有效時(shí)長,例如Expires: Thu, 10 Nov 2015 08:45:11 GMT,二者都可以用。

          Last-Modified也有類似的字段Etag,區(qū)別在于Last-Modified是以時(shí)間做對比,Etag是以文件的哈希值做對比。當(dāng)文件有效時(shí)長過期后,請求服務(wù)器會(huì)在請求頭的If-None-Match字段帶上Etag的值,并交由服務(wù)器對比。

          Cookie處理

          眾所周知,http協(xié)議中是支持cookie設(shè)置的,服務(wù)器可以通過Set-Cookie:字段對瀏覽器設(shè)置cookie,并且還可以指定過期時(shí)間、域名等。這些在Chrome這些瀏覽器中比較適用,但是如果在客戶端內(nèi)進(jìn)行顯示,就需要客戶端傳一些參數(shù)過去,可以讓H5獲取到登錄等狀態(tài)。

          蘋果雖然提供了一些Cookie管理的API,但在WKWebView的使用上還是有很多坑的,最后我會(huì)給出一個(gè)比較通用的方案。

          WKWebView Cookie設(shè)計(jì)

          之前使用UIWebView的時(shí)候,和傳統(tǒng)的cookie管理類NSHTTPCookieStorage讀取的是一塊區(qū)域,或者說UIWebViewcookie也是由此類管理的。但是WKWebViewcookie設(shè)計(jì)不太一樣,和Appcookie并沒有存儲在同一塊內(nèi)存區(qū)域,所以二者需要分開做處理。

          WKWebViewcookieNSHTTPCookieStorage之間也有同步操作,但是這個(gè)同步有明顯的延時(shí),而且規(guī)則不容易琢磨。所以為了代碼的穩(wěn)定性,還是自己處理cookie比較合適。

          WKapp是兩個(gè)進(jìn)程,cookie也是兩份,但是WKcookieapp的沙盒里。有一個(gè)定時(shí)同步,但是并沒有一個(gè)特定規(guī)則,所以最好不要依賴同步。WKcookie變化只有兩個(gè)時(shí)機(jī),一個(gè)是js執(zhí)行代碼setCookie,另一個(gè)是response返回cookie。

          WKWebsiteDataStore

          Cookie的管理一直都是WKWebView的一個(gè)弊端,對于Cookie的處理很不方便。在iOS9中可以通過WKWebsiteDataStoreCookie進(jìn)行管理,但是用起來并不直觀,需要進(jìn)行dataType進(jìn)行篩選并刪除。而且WKWebsiteDataStore自身功能并不具備添加功能,所以對cookie的處理也只有刪除,不能添加cookie。

          if?(@available(iOS?9.0,?*))?{
          ????NSSet?*cookieTypeSet?=?[NSSet?setWithObject:WKWebsiteDataTypeCookies];
          ????[[WKWebsiteDataStore?defaultDataStore]?removeDataOfTypes:cookieTypeSet?modifiedSince:[NSDate?dateWithTimeIntervalSince1970:0]?completionHandler:^{
          ????????
          ????}];
          }

          WKHTTPCookieStore

          iOS11中蘋果在WKWebsiteDataStore的基礎(chǔ)上,為其增加了WKHTTPCookieStore類專門進(jìn)行cookie的處理,并且支持增加、刪除、查詢?nèi)N操作,還可以注冊一個(gè)observercookie的變化進(jìn)行監(jiān)聽,當(dāng)cookie發(fā)生變化后通過回調(diào)的方法通知監(jiān)聽者。

          WKWebsiteDataStore可以獲取H5頁面通過document.cookie的方式寫入的cookie,以及服務(wù)器通過Set-Cookie的方式寫入的cookie,所以還是很推薦使用這個(gè)類來管理cookie的,可惜只支持iOS11。

          下面是給WKWebView添加cookie的一段代碼。

          NSMutableDictionary?*params?=?[NSMutableDictionary?dictionary];
          [params?setObject:@"password"?forKey:NSHTTPCookieName];
          [params?setObject:@"e10adc3949ba5"?forKey:NSHTTPCookieValue];
          [params?setObject:@"www.google.com"?forKey:NSHTTPCookieDomain];
          [params?setObject:@"/"?forKey:NSHTTPCookiePath];
          [params?setValue:[NSDate?dateWithTimeIntervalSinceNow:60*60*72]?forKey:NSHTTPCookieExpires];
          NSHTTPCookie?*cookie?=?[NSHTTPCookie?cookieWithProperties:params];
          [self.cookieWebview.configuration.websiteDataStore.httpCookieStore?setCookie:cookie?completionHandler:nil];

          我公司方案

          處理Cookie最好的方式是通過WKHTTPCookieStore來處理,但其只支持iOS11及以上設(shè)備,所以這種方案目前還不能作為我們的選擇。其次是WKWebsiteDataStore,但其只能作為一個(gè)刪除cookie的使用,并不不能用來管理cookie。

          我公司的方案是,通過iOS8推出的WKUserContentController來管理webViewcookie,通過NSHTTPCookieStorage來管理網(wǎng)絡(luò)請求的cookie,例如H5發(fā)出的請求。通過NSURLSessionNSURLConnection發(fā)出的請求,都會(huì)默認(rèn)帶上NSHTTPCookieStorage中的cookie,H5內(nèi)部的請求也會(huì)被系統(tǒng)交給NSURLSession處理。

          在代碼實(shí)現(xiàn)層面,監(jiān)聽didFinishLaunching通知,在程序啟動(dòng)時(shí)從服務(wù)端請求用戶相關(guān)信息,當(dāng)然從本地取也可以,都是一樣的。數(shù)據(jù)是keyvalue的形式下發(fā),按照key=value的形式拼接,并通過document.cookie組裝成設(shè)置cookiejs代碼,所有代碼拼接為一個(gè)以分號分割的字符串,后面給webViewcookie時(shí)就通過這個(gè)字符串執(zhí)行。

          對于網(wǎng)絡(luò)請求的cookie,通過NSHTTPCookieStorage直接將cookie種到根域名下的,可以對根域名下所有子域名生效,這里的處理比較簡單。

          SVREQUEST.type(SVRequestTypePost).parameters(params).success(^(NSDictionary?*cookieDict)?{
          ????self.cookieData?=?[cookieDict?as:[NSDictionary?class]];
          ????[self?addCookieWithDict:cookieDict?forHost:@".google.com"];
          ????[self?addCookieWithDict:cookieDict?forHost:@".google.cn"];
          ????[self?addCookieWithDict:cookieDict?forHost:@".google.jp"];
          ????
          ????NSMutableString?*scriptString?=?[NSMutableString?string];
          ????for?(NSString?*key?in?self.cookieData.allKeys)?{
          ????????NSString?*cookieString?=?[NSString?stringWithFormat:@"%@=%@",?key,?cookieDict[key]];
          ????????[scriptString?appendString:[NSString?stringWithFormat:@"document.cookie?=?'%@;expires=Fri,?31?Dec?9999?23:59:59?GMT;';",?cookieString]];
          ????}
          ????self.webviewCookie?=?scriptString;
          }).startRequest();

          -?(void)addCookieWithDict:(NSDictionary?*)dict?forHost:(NSString?*)host?{
          ????[dict?enumerateKeysAndObjectsUsingBlock:^(NSString?*?_Nonnull?key,?NSString?*?_Nonnull?value,?BOOL?*?_Nonnull?stop)?{
          ????????NSMutableDictionary?*properties?=?[NSMutableDictionary?dictionary];
          ????????[properties?setObject:key?forKey:NSHTTPCookieName];
          ????????[properties?setObject:value?forKey:NSHTTPCookieValue];
          ????????[properties?setObject:host?forKey:NSHTTPCookieDomain];
          ????????[properties?setObject:@"/"?forKey:NSHTTPCookiePath];
          ????????[properties?setValue:[NSDate?dateWithTimeIntervalSinceNow:60*60*72]?forKey:NSHTTPCookieExpires];
          ????????NSHTTPCookie?*cookie?=?[NSHTTPCookie?cookieWithProperties:properties];
          ????????[[NSHTTPCookieStorage?sharedHTTPCookieStorage]?setCookie:cookie];
          ????}];
          }

          webViewcookie是通過WKUserContentController寫入js的方式實(shí)現(xiàn)的,也就是上面拼接的js字符串。但是這個(gè)類有一個(gè)問題就是不能持久化cookie,也就是cookieuserContentController的聲明周期,如果退出Appcookie就會(huì)消失,下次進(jìn)入App還需要種一次,這是個(gè)大問題。

          所以我司的處理方式是在decidePolicyForNavigationAction:回調(diào)方法中加入下面這段代碼,代碼中會(huì)判斷此域名是否種過cookie,如果沒有則種cookie。對于cookie的處理,我新建了一個(gè)cookieWebview專門處理cookie的問題,當(dāng)執(zhí)行addUserScript后,通過loadHTMLString:baseURL:加載一個(gè)空的本地html,并將域名設(shè)置為當(dāng)前將要顯示頁面的域名,從而使剛才種的cookie對當(dāng)前processPool內(nèi)所有的webView生效。

          這種方案種cookie是同步執(zhí)行的,而且對webView的影響很小,經(jīng)過我的測試,平均添加一次cookie只需要消耗28ms的時(shí)間。從用戶的角度來看是無感知的,并不會(huì)有頁面的卡頓或重新刷新。

          -?(void)setCookieWithUrl:(NSURL?*)url?{
          ????NSString?*host?=?[url?host];
          ????if?([self.cookieURLs?containsObject:host])?{
          ????????return;
          ????}
          ????[self.cookieURLs?addObject:host];
          ????
          ????WKUserScript?*wkcookieScript?=?[[WKUserScript?alloc]?initWithSource:self.webviewCookie
          ??????????????????????????????????????????????????????????injectionTime:WKUserScriptInjectionTimeAtDocumentStart
          ???????????????????????????????????????????????????????forMainFrameOnly:NO];
          ????[self.cookieWebview.configuration.userContentController?addUserScript:wkcookieScript];
          ????
          ????NSString?*baseWebUrl?=?[NSString?stringWithFormat:@"%@://%@",?url.scheme,?url.host];
          ????[self.cookieWebview?loadHTMLString:@""?baseURL:[NSURL?URLWithString:baseWebUrl]];
          }

          刪除cookie的處理則相對比較簡單,NSHTTPCookieStorage通過cookies屬性遍歷到自己需要?jiǎng)h除的NSHTTPCookie,調(diào)用方法將其刪除即可。webView的刪除方法更是簡單粗暴,直接調(diào)用removeAllUserScripts刪除所有WKUserScript即可。

          -?(void)removeWKWebviewCookie?{
          ????self.webviewCookie?=?nil;
          ????[self.cookieWebview.configuration.userContentController?removeAllUserScripts];
          ????
          ????NSMutableArray<NSHTTPCookie?*>?*cookies?=?[NSMutableArray?array];
          ????[[NSHTTPCookieStorage?sharedHTTPCookieStorage].cookies?enumerateObjectsUsingBlock:^(NSHTTPCookie?*?_Nonnull?cookie,?NSUInteger?idx,?BOOL?*?_Nonnull?stop)?{
          ????????if?([self.cookieData.allKeys?containsObject:cookie.name])?{
          ????????????[cookies?addObjectOrNil:cookie];
          ????????}
          ????}];
          ????
          ????[cookies?enumerateObjectsUsingBlock:^(NSHTTPCookie?*?_Nonnull?cookie,?NSUInteger?idx,?BOOL?*?_Nonnull?stop)?{
          ????????[[NSHTTPCookieStorage?sharedHTTPCookieStorage]?deleteCookie:cookie];
          ????}];
          }

          白屏問題

          如果WKWebView加載內(nèi)存占用過多的頁面,會(huì)導(dǎo)致WebContent Process進(jìn)程崩潰,進(jìn)而頁面出現(xiàn)白屏,也有可能是系統(tǒng)其他進(jìn)程占用內(nèi)存過多導(dǎo)致的白屏。對于低內(nèi)存導(dǎo)致的白屏問題,有以下兩種方案可以解決。

          iOS9中蘋果推出了下面的API,當(dāng)WebContent進(jìn)程發(fā)生異常退出時(shí),會(huì)回調(diào)此API??梢栽谶@個(gè)API中進(jìn)行對應(yīng)的處理,例如展示一個(gè)異常頁面。

          -?(void)webViewWebContentProcessDidTerminate:(WKWebView?*)webView;

          如果從其他App回來導(dǎo)致白屏問題,可以在視圖將要顯示的時(shí)候,判斷webView.title是否為空。如果為空則展示異常頁面。


          瀏覽 73
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产91精品在线观看 | 偷拍福利视频网站 | 国产精品久久久久久久鸭 | huangse网站免费在线观看 | 黄色在线观看免费 |