踩坑WKWebView

背景

在iOS應(yīng)用開發(fā)中枪眉,內(nèi)嵌WebView一直占有一定的頁面數(shù)量比例狸驳。它能以較低的開發(fā)成本實現(xiàn)iOS、Android和Web的復(fù)用役耕,也可以一定程度的的規(guī)避蘋果對熱更新的封鎖采转。然而UIWebView的CPU資源消耗和內(nèi)存占用一直被嫌棄,導(dǎo)致很多客戶端中需要動態(tài)更新等頁面時不得不采用其他方案。長遠來看故慈,功能的動態(tài)加載以及三端的融合將會是大趨勢板熊。怎么解決WebView固有的問題呢?我們將通過全面的對比來分析使用UIWebView的問題察绷。

全面對比

  • UIWebView使用UIKit框架干签,而WKWebView使用WebKit.framework。WKWebView采用與Safari 相同的 Nitro JavaScript 引擎拆撼,在cpu資源消耗方面容劳,遠低于UIWebView。在使用WKWebView后闸度,應(yīng)用在打開類似商城首頁這種加載內(nèi)容較多的網(wǎng)頁時竭贩,CPU占用下降非常明顯

  • WKWebView為多進程組件,會從App內(nèi)存中分離內(nèi)存到單獨的進程(Network Process and Rendring Process)中筋岛。當(dāng)內(nèi)存超過了系統(tǒng)分配給WKWebView的內(nèi)存時候娶视,會導(dǎo)致WKWebView瀏覽器崩潰白屏,但是App不會Crash(app會收到系統(tǒng)通知睁宰,并且嘗試去重新加載頁面)肪获。相反UIWebView是和app同一個進程,UIWebView加載頁面占用的內(nèi)存被計算為app內(nèi)存占用的一部分柒傻,當(dāng)app超過了系統(tǒng)分配的內(nèi)存孝赫,則會被操作系統(tǒng)crash。在整個過程中红符,會經(jīng)常收到iOS系統(tǒng)的通知用來防止app被系統(tǒng)kill青柄,很多時候,這些通知并不及時预侯,或者根本沒有返回通知致开。

  • WKWebView是異步處理native與JavaScript之間的通信,所以執(zhí)行速度會更快萎馅。

  • WKWevbView內(nèi)存消耗較UIWebView大幅下降双戳。

  • WKWebView有著高達60fps的滾動刷新率以及內(nèi)置手勢

  • WKWevView更多的支持 HTML5 的特性

  • WKWevView將 UIWebViewDelegate 與 UIWebView 拆分成了14類與3個協(xié)議,包含更細節(jié)功能的實現(xiàn)糜芳,詳解如下:
    WKBackForwardList: 之前訪問過的 web 頁面的列表飒货,可以通過后退和前進動作來訪問到。

    WKBackForwardListItem: webview 中后退列表里的某一個網(wǎng)頁峭竣。

    WKFrameInfo: 包含一個網(wǎng)頁的布局信息塘辅。

    WKNavigation: 包含一個網(wǎng)頁的加載進度信息。

    WKNavigationAction: 包含可能讓網(wǎng)頁導(dǎo)航變化的信息皆撩,用于判斷是否做出導(dǎo)航變化扣墩。

    WKNavigationResponse: 包含可能讓網(wǎng)頁導(dǎo)航變化的返回內(nèi)容信息,用于判斷是否做出導(dǎo)航變化。

    WKPreferences: 概括一個 webview 的偏好設(shè)置呻惕。

    WKProcessPool: 表示一個 web 內(nèi)容加載池盘榨。

    WKUserContentController: 提供使用 JavaScript post 信息和注射 script 的方法。

    WKScriptMessage: 包含網(wǎng)頁發(fā)出的信息蟆融。

    WKUserScript: 表示可以被網(wǎng)頁接受的用戶腳本草巡。

    WKWebViewConfiguration: 初始化 webview 的設(shè)置。

    WKWindowFeatures: 指定加載新網(wǎng)頁時的窗口屬性型酥。

    WKNavigationDelegate: 提供了追蹤主窗口網(wǎng)頁加載過程和判斷主窗口和子窗口是否進行頁面加載新頁面的相關(guān)方法山憨。

    WKUIDelegate: 提供用原生控件顯示網(wǎng)頁的方法回調(diào)。

    WKScriptMessageHandler: 提供從網(wǎng)頁中收消息的回調(diào)方法弥喉。

  • 在使用cookie方面郁竟,在使用 UIWebVIew 的時候我們并不關(guān)注 Cookie,因為在調(diào)用登錄接口的時候無論是AFNetworking由境,還是其他棚亩,登錄成功之后都會自動保存在[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies 中。但 WKWebView 的存儲體系與 UIWebVIew 完全不一樣虏杰,只能手動給它添加 Cookie讥蟆。這也是很多同行所詬病的地方,甚至因為這個原因而遲遲不愿意更新纺阔,下面貼出代碼供參考

private func configWebView() -> WKWebView {
    let webConfig = WKWebViewConfiguration()
    
    // 1.刪除沙盒中 之前舊版本的cookies以及現(xiàn)在的cookie  2.實時從登錄信息中組合cookies瘸彤,不以沙盒中的cookie為準
    if #available(iOS 11.0, *) {
        /// iOS 11以上
        let webView = WKWebView.init(frame: .zero, configuration: webConfig)
        let store = webConfig.websiteDataStore.httpCookieStore
        store.getAllCookies { (items) in
            for item in items {
                if let comment = item.comment, comment.contains("fc_") {
                    FCLog("開始刪除:\(item.domain)____\(item.name)")
                    store.delete(item, completionHandler: {
                        FCLog("\(item.domain) 刪除了")
                    })
                }
            }
            for item in HttpCookieManager.cookies {
                store.setCookie(item, completionHandler: nil)
            }
        }
        return webView
    } else {
        // iOS 11以下
        let userContentController = WKUserContentController()
        webConfig.userContentController = userContentController
        for str in HttpCookieManager.getAllCookies() {
            let setCookie = "document.cookie='\(str)';"
            let cookieScript = WKUserScript.init(source: setCookie, injectionTime: .atDocumentStart, forMainFrameOnly: false)
            userContentController.addUserScript(cookieScript)
        }
        let webView = WKWebView.init(frame: .zero, configuration: webConfig)
        return webView
    }
}
  • WKWebView與 UIWebView 機制不同:加載過程中所有的請求都不經(jīng)過 NSURLProtocol,也就是WKWebView無法攔截響應(yīng)數(shù)據(jù)
  • 對于 WKWebView 笛钝,有三個屬性支持KVO质况,因此我們可以輕松監(jiān)聽其值的變化,分別是:loading玻靡、title结榄、estimatedProgress,對應(yīng)功能表示為:是否正在加載中、頁面的標題囤捻、頁面內(nèi)容加載進度(值為0.0~1.0)臼朗,下面貼出我們實際在項目中kvo監(jiān)聽的運用
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    
    // 監(jiān)聽 WKWebView 對象的 estimatedProgress 屬性,就是當(dāng)前網(wǎng)頁加載的進度
    webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
    
    // 監(jiān)聽 WKWebView 對象的 title 屬性最蕾,就是當(dāng)前網(wǎng)頁title
    webView.addObserver(self, forKeyPath: "title", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if let x = change?[.newKey], let keyPath = keyPath {
        switch keyPath {
        case "estimatedProgress":
            if let progress = x as? Float {
                print("progress is \(progress)")
                progressView.setProgress(progress, animated: true)
            }
        case "title":
            if let title = x as? String, let action = didGetTitleAction {
                action(title)
            }
        default:
            print("observeValue:\(x)")
        }
    }
}

業(yè)務(wù)場景

native與JS的相互調(diào)用

WKWebView對于HTML5的操作已經(jīng)很便捷了依溯,但是還沒有Android的WebView那樣簡單老厌。WebView能夠直接注入JavaScript對象瘟则,交互過程中Java 與 JavaScript甚至可以直接調(diào)用對方的方法,不用攔截枝秤,不用分發(fā)醋拧,這樣的Java 與 JavaScript的交互非常清晰明了。在iOS上,還達不到這樣的便捷丹壕。
在使用WKWebView時庆械,H5調(diào)用Native 的過程是:1、Native注入JavaScript函數(shù)菌赖;2缭乘、Native實現(xiàn)橋接方法:通過系統(tǒng)方法攔截JavaScript事件,匹配OC/Swift注冊列表琉用,分發(fā)調(diào)用不同的原生方法堕绩。附上代碼:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let url = navigationAction.request.url else {
        decisionHandler(.allow)
        return
    }
    guard let urlString = url.absoluteString.removingPercentEncoding else {
        decisionHandler(.allow)
        return
    }
    // 現(xiàn)有業(yè)務(wù)協(xié)議,與H5約定命名邑时,可攜帶參數(shù)
    guard urlString.contains("JS協(xié)議"),
        let preRange = urlString.range(of: "JS協(xié)議") else {
            decisionHandler(.allow)
            return
    }
    
    let jsonStr = urlString[preRange.upperBound..<urlString.endIndex]
    guard let jsonData = jsonStr.data(using: .utf8) else {
        decisionHandler(.allow)
        return
    }
    let json = JSON(jsonData)
    guard !json.isEmpty else {
        decisionHandler(.allow)
        return
    }
    decisionHandler(.allow)
}

而OC/Swift調(diào)用JavaScript的過程是:使用WKWebView的接口調(diào)用JavaScript函數(shù)奴紧。附上代碼

self.webView.evaluateJavaScript(javaScript, completionHandler: { (_, error) in
    if let error = error {
        FCLog("webJS——\(javaScript)執(zhí)行失敗: \(error.localizedDescription)")
    }
})

動態(tài)加載并運行JS代碼

附上示例代碼

// js代碼片段
let jsStr = "var clickBtn = document.getElementsByClassName('click_fcbox');for(var j = 0;j < clickBtn.length; j++){clickBtn[j].onclick = function(){this.removeAttribute('clicked');}}"

// 根據(jù)JS字符串初始化WKUserScript對象
let testScript = WKUserScript(source: jsStr, injectionTime:.atDocumentEnd, forMainFrameOnly: true)
let testController = WKUserContentController()
testController.addUserScript(testScript)

// 根據(jù)生成的WKUserScript對象晶丘,初始化WKWebViewConfiguration
let webConfiguration = WKWebViewConfiguration()
webConfiguration.userContentController = testController
let testWebview = WKWebView(frame: CGRect.zero, configuration: webConfiguration)
view.addSubview(testWebview)

踩坑實錄

  • 對于JS有異步接口回調(diào)的數(shù)據(jù)情況黍氮,可能導(dǎo)致頁面加載數(shù)據(jù)異常,除了延時機制外浅浮,還沒找到好的解決辦法沫浆。有高招的大神,求指導(dǎo)滚秩。
  • 創(chuàng)建WKWebViewConfiguration的實例递惋,這個實例可以給網(wǎng)頁進行一些配置。注意這個實例只能在 webView第一次創(chuàng)建的時候才能使用带射。
  • WKWebView 點擊H5內(nèi)鏈接無反應(yīng)霎苗,多半是因為網(wǎng)頁中有target="_blank" 在新窗口打開連接,此時我們需要設(shè)置WKWebView的另外一個代理WKUIDelegate已艰,并實現(xiàn)代理方法如下:
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
       webView.load(navigationAction.request)
       return nil
   }
  • App之間的數(shù)據(jù)交互是通過URL Scheme來實現(xiàn)的痊末。在UIWebView時代如果加載的URL是“customURLScheme://”這種形式的,UIWebView會執(zhí)行openUrl函數(shù)哩掺,從而和其他App進行交互凿叠,然而WKWebView就需要自己支持了,我們要對非http://和https://的做相應(yīng)處理嚼吞,附上代碼:
if !(urlString.hasPrefix("http://") || urlString.hasPrefix("https://")) {
    if UIApplication.shared.canOpenURL(appUrl) {
        UIApplication.shared.openURL(appUrl)
    }
}
  • WKWebView不會像UIWebView把Content-Type標頭設(shè)置為POST請求的application / x-www-formurlencoded盒件,都要手動添加,否則會造成所加載H5頁面出錯
let request = NSMutableURLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舱禽,一起剝皮案震驚了整個濱河市炒刁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誊稚,老刑警劉巖翔始,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罗心,死亡現(xiàn)場離奇詭異,居然都是意外死亡城瞎,警方通過查閱死者的電腦和手機渤闷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脖镀,“玉大人飒箭,你說我怎么就攤上這事⊙鸦遥” “怎么了补憾?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卷员。 經(jīng)常有香客問我盈匾,道長,這世上最難降的妖魔是什么毕骡? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任削饵,我火速辦了婚禮,結(jié)果婚禮上未巫,老公的妹妹穿的比我還像新娘窿撬。我一直安慰自己,他們只是感情好叙凡,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布劈伴。 她就那樣靜靜地躺著,像睡著了一般握爷。 火紅的嫁衣襯著肌膚如雪跛璧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天新啼,我揣著相機與錄音追城,去河邊找鬼。 笑死燥撞,一個胖子當(dāng)著我的面吹牛座柱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播物舒,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼色洞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冠胯?” 一聲冷哼從身側(cè)響起火诸,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涵叮,沒想到半個月后惭蹂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡割粮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年盾碗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舀瓢。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡廷雅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出京髓,到底是詐尸還是另有隱情航缀,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布堰怨,位于F島的核電站芥玉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏备图。R本人自食惡果不足惜灿巧,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揽涮。 院中可真熱鬧抠藕,春花似錦、人聲如沸蒋困。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雪标。三九已至零院,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間村刨,已是汗流浹背门粪。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烹困,地道東北人玄妈。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像髓梅,于是被迫代替她去往敵國和親拟蜻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容