<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

          共 21602字,需瀏覽 44分鐘

           ·

          2021-11-08 03:19

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????


          作者丨搜狐視頻 劉壯

          來源丨搜狐技術(shù)產(chǎn)品(ID:sohu-tech)

            

          本文字?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相對(duì)于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. 通過域名的方式請(qǐng)求服務(wù)器,請(qǐng)求前瀏覽器會(huì)做一個(gè)DNS解析,并將IP地址返回給瀏覽器。
          2. 瀏覽器使用IP地址請(qǐng)求服務(wù)器,并且開始握手過程。TCP是三次握手,如果使用https則還需要進(jìn)行TLS的握手,握手后根據(jù)協(xié)議字段選擇是否保持連接。
          3. 握手完成后,瀏覽器向服務(wù)端發(fā)送請(qǐng)求,獲取html文件。
          4. 服務(wù)器解析請(qǐng)求,并由CDN服務(wù)器返回對(duì)應(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ù),請(qǐng)求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ì)對(duì)DOM樹進(jìn)行修改。
          7. 解析完html,并執(zhí)行完js代碼,形成最終的DOM樹。通過DOM配合css文件找出每個(gè)節(jié)點(diǎn)的最終展示樣式,并交由瀏覽器進(jìn)行渲染展示
          8. 結(jié)束鏈接。

          代理方法

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

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

          1. 重定向的回調(diào),可以在請(qǐng)求重定向時(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ù),表示是否允許頁面加載。這里可以對(duì)域名進(jìn)行判斷,如果是站外域名,則可以提示用戶是否進(jìn)行跳轉(zhuǎn)。如果是跳轉(zhuǎn)其他App或商店的URL,則可以通過openURL進(jìn)行跳轉(zhuǎn),并將這次請(qǐng)求攔截。包括cookie的處理也在此方法中完成,后面會(huì)詳細(xì)講到cookie的處理。

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

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

          開始加載頁面,并請(qǐng)求服務(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é)議的對(duì)象,即可接收js的回調(diào),由于userContent會(huì)強(qiáng)引用傳入的對(duì)象,所以應(yīng)該是新創(chuàng)建一個(gè)對(duì)象,而不是self。注冊(cè)對(duì)象時(shí),后面的name就是js調(diào)用的函數(shù)名。

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

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

          [userContent removeScriptMessageHandlerForName:@"clientCallback"];

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

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

          調(diào)用

          原生調(diào)用H5的方法也是一樣,創(chuàng)建一個(gè)WKUserScript對(duì)象,并將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對(duì)于執(zhí)行js代碼提供了兩種方式,通過userContent添加一個(gè)WKUserScript對(duì)象的方式,以及通過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ù)場景去選擇對(duì)應(yīng)的API。

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

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

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

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

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

          對(duì)調(diào)用方提供js交互、webView生命周期、加載錯(cuò)誤等回調(diào),外接通過對(duì)應(yīng)的回調(diào)進(jìn)行處理。這些回調(diào)都是可選的,不實(shí)現(xiàn)對(duì)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拼接、headerjs注入等方式添加,這個(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屬性,可以指定對(duì)應(yīng)的進(jìn)程池對(duì)象。每個(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è)單例對(duì)象。所有webView創(chuàng)建的時(shí)候,都使用同一個(gè)內(nèi)容進(jìn)程,即可實(shí)現(xiàn)資源共享。

          UserAgent

          User-Agent是在http協(xié)議中的一個(gè)請(qǐng)求頭字段,用來告知服務(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寫入的方式對(duì)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ái)等系統(tǒng)生命周期。

          js調(diào)用客戶端為例,有兩個(gè)緯度的調(diào)用??梢酝ㄟ^URLRouter的方式直接調(diào)用某個(gè)模塊,這種調(diào)用方式遵循客戶端的URL定義即可調(diào)起,并且支持傳參。還可以通過userContentController的方式,進(jìn)行頁面級(jí)的調(diào)用,例如關(guān)閉webView、調(diào)起登錄功能等,也就是通過js調(diào)用客戶端的某個(gè)功能,這種方式需要客戶端提供對(duì)應(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)前頁面等,直接由對(duì)應(yīng)的功能類來處理調(diào)用,其他的時(shí)間應(yīng)該交給外界處理。

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

          @interface WKWebViewCallbackModel : NSObject
          @property(nonatomicstrongWKWebViewController *webViewVC;
          @property(nonatomicstrongWKCallType *type;
          @property(nonatomiccopyNSDictionary *parameters;
          @property(nonatomiccopyNSString *callbackID;
          @property(nonatomiccopyNSString *callbackFunction;
          @end

          持久化

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

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

          緩存機(jī)制

          緩存規(guī)則

          前端瀏覽器包括WKWebView在內(nèi),為了保證快速打開頁面,減少用戶流量消耗,都會(huì)對(duì)資源進(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ì)請(qǐng)求服務(wù)器數(shù)據(jù),請(qǐng)求會(huì)失敗。
          • NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, 忽略本地以及路由過程中的緩存,從服務(wù)器獲取最新數(shù)據(jù)。
          • NSURLRequestReloadRevalidatingCacheData = 5, 從服務(wù)端驗(yàn)證緩存是否可用,本地不可用則請(qǐng)求服務(wù)端數(shù)據(jù)。
          • NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,


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

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

          緩存文件

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

          • WKWebsiteDataTypeFetchCache,磁盤中的緩存,根據(jù)源碼可以看出,類型是DOMCache
          • WKWebsiteDataTypeDiskCache,本地磁盤緩存,和fetchCache的實(shí)現(xiàn)不同,是所有的緩存數(shù)據(jù)
          • WKWebsiteDataTypeMemoryCache,本地內(nèi)存緩存
          • WKWebsiteDataTypeOfflineWebApplicationCache,離線web應(yīng)用程序緩存
          • WKWebsiteDataTypeCookies,cookie緩存
          • WKWebsiteDataTypeSessionStoragehtml會(huì)話存儲(chǔ)
          • WKWebsiteDataTypeLocalStorage,html本地?cái)?shù)據(jù)緩存
          • WKWebsiteDataTypeWebSQLDatabasesWebSQL數(shù)據(jù)庫數(shù)據(jù)
          • WKWebsiteDataTypeIndexedDBDatabases,數(shù)據(jù)庫索引
          • WKWebsiteDataTypeServiceWorkerRegistrations,服務(wù)器注冊(cè)數(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對(duì)象,此對(duì)象包含域名和類型兩個(gè)參數(shù)。可以根據(jù)域名和類型進(jìn)行判斷,隨后調(diào)用removeDataOfTypes:方法傳入需要?jiǎng)h除的對(duì)象,對(duì)指定域名下的數(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í)長,例如請(qǐng)求文件后服務(wù)器響應(yīng)頭返回Cache-Control:max-age=600,則表示文件有效時(shí)長600秒。所以此文件在有效時(shí)長內(nèi),都不會(huì)發(fā)出網(wǎng)絡(luò)請(qǐng)求,直到過期為止。
          • Last-Modified:請(qǐng)求文件后服務(wù)器響應(yīng)頭中返回的,表示文件的最新更新時(shí)間。如果Cache-Control過期后,則會(huì)請(qǐng)求服務(wù)器并將這個(gè)時(shí)間放在請(qǐng)求頭的If-Modified-Since字段中,服務(wù)器收到請(qǐng)求后會(huì)進(jìn)行時(shí)間對(duì)比,如果時(shí)間沒有發(fā)生改變則返回304,否則返回新的文件和響應(yīng)頭字段,并返回200

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

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

          Cookie處理

          眾所周知,http協(xié)議中是支持cookie設(shè)置的,服務(wù)器可以通過Set-Cookie:字段對(duì)瀏覽器設(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并沒有存儲(chǔ)在同一塊內(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è)弊端,對(duì)于Cookie的處理很不方便。在iOS9中可以通過WKWebsiteDataStore對(duì)Cookie進(jìn)行管理,但是用起來并不直觀,需要進(jìn)行dataType進(jìn)行篩選并刪除。而且WKWebsiteDataStore自身功能并不具備添加功能,所以對(duì)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操作,還可以注冊(cè)一個(gè)observer對(duì)cookie的變化進(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ò)請(qǐng)求的cookie,例如H5發(fā)出的請(qǐng)求。通過NSURLSessionNSURLConnection發(fā)出的請(qǐng)求,都會(huì)默認(rèn)帶上NSHTTPCookieStorage中的cookie,H5內(nèi)部的請(qǐng)求也會(huì)被系統(tǒng)交給NSURLSession處理。

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

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

          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];
              }];
          }

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

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

          這種方案種cookie是同步執(zhí)行的,而且對(duì)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的處理則相對(duì)比較簡單,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)致的白屏。對(duì)于低內(nèi)存導(dǎo)致的白屏問題,有以下兩種方案可以解決。

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

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

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

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 54
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  橾逼网站 | 黄色免费国产视频 | 一区二区三区免费播放 | 国产一级网络免费黄色片 | 激情深爱五月天 |