一種新的頁面載入時間檢測方式
在一次組內會議中,被分配到了這樣一個技術研究需求,目的是通過檢測頁面載入耗時,來對頁面進行鍼對性的優化.拿到這個任務之後,立馬去搜集了一些網上現有的資料,並作出了一些總結.
目前實現檢測的幾種方式
基本思路
通常是利用swizlling
在viewDidLoad
方法裡儲存一個初始時間,然後在viewDidAppear
裡得到頁面出現的時間.
@implementation UIViewController (LoadTime) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ swizzleMethod([UIViewController class], @selector(viewDidLoad), @selector(my_viewDidLoad)); }); swizzleMethod([UIViewController class], @selector(viewDidAppear), @selector(my_viewDidAppear:)); }); } - (void)my_viewDidLoad { NSDate *date = [NSDate date]; // 儲存開始時間 _date = date; [self my_viewDidLoad]; } - (void)my_viewDidAppear:(BOOL)animated{ [self my_viewDidAppear:animated]; // 得到載入時間 NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:_date]; NSLog(@"Page %@ cost %g to be appeared", [self class], duration); } @end 複製程式碼
一種基於KVO的頁面載入時間獲取(作者五子棋)
這篇部落格指出了上述方法的問題,你hook的其實是父類UIViewController
的方法,子類其實是呼叫了[super xxxxxx]
方法,這種處理方式沒辦法對每種頁面都進行處理,需要各自建立對應分類.
於是作者突發奇想,利用KVO拿到派生的子類進行IMP
的替換,從而解決了這個問題.以上兩種方式只能得出程式碼載入時間,如果某些頁面和網路有關,網路請求這部分時間就很難拿到了.
「無侵入頁面載入完成檢測」的一些思路(作者Limboy)
這個方法是我完全沒有想到的一種處理方式.利用影象純色佔比
來判斷當前頁面是否是載入完成.簡單來說,就是開啟一個CADisplayLink
定時器,對當前頁面進行截圖,然後利用計算純色佔比的演算法算出比例,當比例大於某一個閾值,就說明頁面已經載入成功了.這種方法我覺得是最直觀的方法,但作者也列舉了一些問題:
1.需要主動去截圖檢測,而不能載入完成後告知。這其中的差別在於無法得知具體哪個時間載入完成了。
2.有些頁面被故意設計成有較多留白,這時就不容易判斷了。
3.「未載入完成」不同的頁面會有不同的表現。
4.當用戶滑動時,有可能之前的頁面已經載入了
美團Hertz的思路
這篇文章介紹了美團關於效能監控的一些措施,也提到了iOS中頁面載入時間檢測的方式:在iOS中我們採取了不同的做法,Hertz在配置檔案中指定最終渲染頁面的某個元素的tag,並在網路請求成功後開啟CADisplayLink檢查該元素是否出現在根節點下面。
總結下來的三個問題
- 問題1 :即使解決了無法直接hook子類的實現,但是也不能得到確切的載入時間如下面的例子:
- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; // 模擬了一個非同步網路請求 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(3); dispatch_async(dispatch_get_main_queue(), ^{ TempView *tView = [[TempView alloc] init]; tView.frame = CGRectMake(10, 20, 300, 200); tView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:tView]; }); }); } 複製程式碼
在這個例子中,使用者所感受到的載入完成應該是tView
在網路請求之後顯示的時間.單純的hook生命週期方法是無法獲取網路延遲這段時間的.
- 問題2 :如果開啟定時器進行頁面檢測截圖,消耗記憶體的同時也會影響頁面渲染,可能會造成效能問題.
-
問題3
:美團這種思路是可行的,但是這個
tag
該怎麼打呢,如果是一個純tableView
的控制器顯示該怎麼判定呢,webview
呢?.
新的思路
這裡我借鑑了美團的思路,但又有所不同.
我們先弄清楚2個問題:
1.我們檢測頁面載入時長的目的主要是為了檢測某些頁面
從載入到顯示的時間,通常頁面的出現除了頁面自身的渲染出現,還伴隨著介面資料的重新整理,有些介面依賴網路請求,有些介面依賴本地讀取或者直接顯示靜態頁面.
2.並不是所有的介面都需要進行檢測,我們應該把監測重點放到一些使用者常用的介面上,當然,能覆蓋越多越能發現更多可能的問題.
所以我們只需要檢測某個控制器中的某個關鍵子view
出現,就可以確定這個時間.那我們怎麼判斷這些子頁面真的顯示呢?
visibleCells
知道如何判斷頁面顯示了,那麼我們需要一個配置檔案,來指定那些頁面關鍵子view
的型別和其它屬性.為了能夠靈活配置,建議通過後臺介面下發一個json,當然也可以本地配置一個dictionary.
我這裡的檔案格式如下:
/* TargetSubview:關鍵子view TargetSubviewType:子view的型別0:UITableView/UICollectionView 1:NormalView 2:Webview TargetEmptyViewType:可能會有的空白view型別 */ @"ViewController":@{ @"TargetSubview" : @"UITableView", @"TargetSubviewType" : @(0), @"TargetEmptyViewType":@"NoDataView" }, @"TempViewController":@{ @"TargetSubview" : @"TempView", @"TargetSubviewType" : @(1), @"TargetEmptyViewType":@"NoDataView" }, @"TempWebviewController":@{ @"TargetSubview" : @"WKWebView", @"TargetSubviewType" : @(2), @"TargetEmptyViewType":@"NoDataView" } 複製程式碼
然後我們就可以hook UIViewController
的viewDidLoad
方法,拿到初始時間,同時開啟一個CADisplayLink
定時器進行檢測.
在定時器的方法裡,我們就開始根據需要檢測的頁面,找到目標的子view,然後根據view型別進行相應的判斷即可.如果符合判斷條件,就可以進行上報了.
注意點:
1.對於空白頁的處理,需要考慮多種情況,例如是直接加在關鍵子view裡還是加在控制器中.
2.啟動的廣告頁是否對首頁載入有影響.
3.這裡遍歷子控制元件的時候,注意子控制元件層次不能太深,最好是一層,不然可能超過16.7ms,造成誤報,這種情況是一個比較蛋疼的點,需要我們去控制子view的層級,但是為了更精準的獲得載入時間, 這一步也很值得,這也是說為啥要用後臺介面去控制,就是為了在業務發生變化之後能靈活調整view的層級.
以上就是我的檢測思路,並且專案中已經運行了幾個版本,中間也發現了不少問題,並得以解決. 這裡有一個比較簡單的demo ,可以讓大家瞭解一下,有啥問題,歡迎指正!