WebKit-WKWebView
iOS8開始蘋果推薦使用WKWebview作為H5開發的核心元件,以替代原有的UIWebView,以下是webkit基本介紹介紹:
介紹部落格 Webkit
H5 - window.performance
window.performance 是W3C效能小組引入的新的API,主流瀏覽器都支援
iOS可以獲取的欄位可以通過xcode官方文件檢視:(WebKit JS
只有做Safari程式設計才能使用,所以只能檢視)
W3C的Performance的時間前後順序如下:
屬性說明:
navigationStart:瀏覽器處理當前網頁的啟動時間
fetchStart:瀏覽器發起http請求讀取文件的毫秒時間戳。
domainLookupStart:域名查詢開始時的時間戳。
domainLookupEnd:域名查詢結束時的時間戳。
connectStart:http請求開始向伺服器傳送的時間戳。
connectEnd:瀏覽器與伺服器連線建立(握手和認證過程結束)的毫秒時間戳。
requestStart:瀏覽器向伺服器發出http請求時的時間戳。或者開始讀取本地快取時。
responseStart:瀏覽器從伺服器(或讀取本地快取)收到第一個位元組時的時間戳。
responseEnd:瀏覽器從伺服器收到最後一個位元組時的毫秒時間戳。
domLoading:瀏覽器開始解析網頁DOM結構的時間。
domInteractive:網頁dom樹建立完成,開始載入內嵌資源的時間。
domContentLoadedEventStart:網頁DOMContentLoaded事件發生時的時間戳。
domContentLoadedEventEnd:網頁所有需要執行的指令碼執行完成時的時間,domReady的時間。
domComplete:網頁dom結構生成時的時間戳。
loadEventStart:當前網頁load事件的回撥函式開始執行的時間戳。
loadEventEnd:當前網頁load事件的回撥函式結束執行時的時間戳。
通過程式碼獲取資料
直接上程式碼:
通過在wkwebview的didFinish方法中使用自定義的jsTiming方法:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.jsTiming()
}
jsTiming()的原始碼
import WebKit
protocol MyWebViewTimingProtocal {
func jsTiming()
}
extension WKWebView : MyWebViewTimingProtocal{
/// 獲取WebView的JS的效能資料
func jsTiming() {
let webView : WKWebView? = self
if #available(iOS 10.0, *) {
webView?.evaluateJavaScript("JSON.stringify(window.performance.timing.toJSON())") { (timingStr, error) in
if error == nil && timingStr != nil {
JSTimingTool.parseJSTimingString(timingStr as! String)
} else {
print("WKWebView Load Performance JS Faild!")
}
}
} else {
let jsFuncStr = "function flatten(obj) {"
+ "var ret = {}; "
+ "for (var i in obj) { "
+ "ret[i] = obj[i];"
+ "}"
+ "return ret;}"
webView?.evaluateJavaScript(jsFuncStr) { (resultStr, error) in
if error == nil && resultStr != nil {
webView?.evaluateJavaScript("JSON.stringify(flatten(window.performance.timing))", completionHandler: { (timingStr, error) in
if error == nil && timingStr != nil {
JSTimingTool.parseJSTimingString(timingStr as! String)
} else {
print("WKWebView Load Performance JS Faild!")
}
})
} else {
print("WKWebView evaluateJavaScript Faild!")
}
}
}
}
}
/// 解析window.performance的工具類
private class JSTimingTool {
/// 解析入口方法
///
/// - Parameter timingStr:window.performance.timing字串
static func parseJSTimingString(_ timingStr: String) {
if let dict = JSTimingTool.dictionaryFromString(timingStr) {
JSTimingTool.parseJSTimingDictionary(dict)
} else {
print("Performance JS trans to Dictionary Faild!")
}
}
/// 字串轉字典
///
/// - Parameter str: 需要轉換的字串
/// - Returns: 轉換完成的字典
static func dictionaryFromString(_ str: String) -> [String : Any]?{
let data = str.data(using: String.Encoding.utf8)
if let dict = try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String : Any] {
return dict
}
return nil
}
/// 分析效能資料字典
///
/// - Parameter dict: window.performance.timing字典
static func parseJSTimingDictionary(_ dict: Dictionary<String, Any>) {
print("\(String(describing: dict))")
let domainLookupStart = dict["domainLookupStart"] as! CLongLong
let domainLookupEnd = dict["domainLookupEnd"] as! CLongLong
let connectStart = dict["connectStart"] as! CLongLong
let connectEnd = dict["connectEnd"] as! CLongLong
let responseStart = dict["responseStart"] as! CLongLong
let responseEnd = dict["responseEnd"] as! CLongLong
let domInteractive = dict["domInteractive"] as! CLongLong
let domComplete = dict["domComplete"] as! CLongLong
let fetchStart = dict["fetchStart"] as! CLongLong
let domLoading = dict["domLoading"] as! CLongLong
let domContentLoadedEventEnd = dict["domContentLoadedEventEnd"] as! CLongLong
let loadEventStart = dict["loadEventStart"] as! CLongLong
let loadEventEnd = dict["loadEventEnd"] as! CLongLong
let dnstiming = domainLookupEnd - domainLookupStart //DNS查詢耗時
let tcptiming = connectEnd - connectStart //TCP連結耗時
let requesttiming = responseEnd - responseStart //request請求耗時
let domtiming = domComplete - domInteractive //解析dom樹耗時
let wheetScreentiming = domLoading - fetchStart //白屏時間
let domreadytiming = domContentLoadedEventEnd - fetchStart //dom ready時間
let domloadtiming = loadEventEnd - loadEventStart //dom load時間
let onloadtiming = loadEventEnd - fetchStart //onload總時間
print("dnstiming:\(dnstiming)\ntcptiming:\(tcptiming)\nrequesttiming:\(requesttiming)\ndomtiming:\(domtiming)\nwheetScreentiming:\(wheetScreentiming)\ndomreadytiming:\(domreadytiming)\ndomloadtiming:\(domloadtiming)\nonloadtiming:\(onloadtiming)\n")
}
}
示例
以http://www.baidu.com為例獲取到的資料
["navigationStart": 1563415353543, "connectStart": 1563415353858, "redirectStart": 0,
"unloadEventEnd": 0, "loadEventStart": 1563415358406,
"responseEnd": 1563415354271, "domainLookupEnd": 1563415353857, "redirectEnd": 0,
"connectEnd": 1563415353921, "secureConnectionStart": 1563415353888,
"unloadEventStart": 0, "domContentLoadedEventStart": 1563415354271,
"responseStart": 1563415354218, "loadEventEnd": 1563415358406,
"domInteractive": 1563415354271, "requestStart": 1563415353921,
"domComplete": 1563415358406, "domLoading": 1563415354231, "fetchStart": 1563415353852,
"domContentLoadedEventEnd": 1563415354271, "domainLookupStart": 1563415353855]
dnstiming:2
tcptiming:63
requesttiming:53
domtiming:4135
wheetScreentiming:379
domreadytiming:419
domloadtiming:0
onloadtiming:4554
全域性監聽
如果需要針對所有頁面都監控,可以使用runtime機制,監聽webview的didFinish方法,通過AOP方式hook到對應的自定義didFinish方法,然後在自定義的didFinish方法中呼叫jsTiming方法
侷限性
window.performance只能在webview的didFinish方法中監聽一次,如果H5頁面內部做跳轉,是無法監聽到的,所以更適合做首次載入的效能分析,如果有二級H5頁面的的效能監聽需求,還是需要前端開發同學進行協助。