背景
在一次組內會議中,被分配到了這樣一個技術研究需求,目的是通過檢測頁面載入耗時,來對頁面進行鍼對性的優化.拿到這個任務之後,立馬去搜集了一些網上現有的資料,並作出了一些總結.
目前實現檢測的幾種方式
基本思路
通常是利用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
出現,就可以確定這個時間.那我們怎麼判斷這些子頁面真的顯示呢?
- 如果該頁面主要子view是UITableView或者UICollectionView,那麼可以指定找到這個頁面的這兩種子類,通過
visibleCells
是否大於0來判斷. - 如果該頁面主要子view的型別是其他view,那麼根據這個view是否出現在螢幕上來判斷.
- 如果該頁面主要子view型別是webview,那麼可以根據webview是否載入url並且是否loading來判斷(有更好的方式可以告訴我).
知道如何判斷頁面顯示了,那麼我們需要一個配置檔案,來指定那些頁面關鍵子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,可以讓大家瞭解一下,有啥問題,歡迎指正!