iOS-监听原生H5性能数据window.performance

WebKit-WKWebView

iOS8开始苹果推荐使用WKWebview做为H5开发的核心组件,以替代原有的UIWebView,如下是webkit基本介绍介绍: 介绍博客 Webkit 前端

H5 - window.performance

window.performance 是W3C性能小组引入的新的API,主流浏览器都支持 iOS能够获取的字段能够经过xcode官方文档查看:(WebKit JS只有作Safari编程才能使用,因此只能查看) web

W3C的Performance的时间先后顺序以下: 编程

属性说明:json

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事件的回调函数结束运行时的时间戳。xcode

经过代码获取数据

直接上代码: 经过在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页面的的性能监听需求,仍是须要前端开发同窗进行协助。dom

相关文章
相关标签/搜索