學(xué)習(xí) YYKit 代碼時,發(fā)現(xiàn) ibireme 在項目里加入的一個查看當(dāng)前屏幕幀數(shù)的小工具,效果如下:
挺實用,實現(xiàn)方法也很簡單,但是思路特別棒但狭。
Demo: YYFPSLabel
這里是我在學(xué)習(xí) YYKit
大牛代碼的過程中的收貨披诗,順便做個筆記:
一、FPSLabel 實現(xiàn)思路:
-
CADisplayLink
默認(rèn)每秒 60次立磁; - 將
CADisplayLink
add 到mainRunLoop
中呈队; - 使用
CADisplayLink
的timestamp
屬性,在CADisplayLink
每次 tick 時唱歧,記錄上一次的timestamp
宪摧; - 用 _count 記錄
CADisplayLink
tick 的執(zhí)行次數(shù); - 計算此次 tick 時,
CADisplayLink
的當(dāng)前 timestamp 和 _lastTimeStamp 的差值颅崩; - 如果差值大于1几于,fps = _count / delta,計算得出 FPS 數(shù)沿后;
詳見 代碼沿彭。
二、NSTimer尖滚、CADisplayLink 常見問題:
2.1 問題一: UIScrollView 在滑動時喉刘,timer 會被暫停的問題。
- 原因:
runloop mode
導(dǎo)致漆弄。iOS處理滑動時睦裳,mainloop
中UIScrollView 的 mode 是UITrackingRunLoopMode
,會優(yōu)先保證界面流暢撼唾,而 timer 默認(rèn)的 model是NSDefaultRunLoopMode
廉邑,所以會出現(xiàn)被暫停。 - 解決辦法:將 timer 加到
NSRunLoopCommonModes
中。
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
詳見:深入理解RunLoop 一文中關(guān)于 定時器 和 RunLoop 的 Mode 的部分
2.2 問題二:NSTimer 對于 target 的循環(huán)引用問題:
以下代碼很常見:
CADisplayLink *_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
[NSTimer timerWithTimeInterval:1.f target:self selector:@selector(tick:) userInfo:nil repeats:YES];
- 原因:以上兩種用法鬓催,都會對 self 強引用肺素,此時 timer持有 self,self 也持有 timer宇驾,循環(huán)引用導(dǎo)致頁面 dismiss 時倍靡,雙方都無法釋放,造成循環(huán)引用课舍。此時使用 __weak 也不能有效解決:
__weak typeof(self) weakSelf = self;
_link = [CADisplayLink displayLinkWithTarget:weakSelf selector:@selector(tick:)];
效果如下:
可以看到 頁面 dismiss 后塌西,計時器仍然在打印
2.3 解決辦法1:在頁面退出前,或者合適的時候筝尾,手動停止 timer捡需,結(jié)束循環(huán)引用。
注意:在 dealloc 方法中是肯定不行的筹淫!由于循環(huán)引用站辉,dealloc 方法不會進。
2.4 解決辦法:2:YYFPSLabel
作者提供的 YYWeakProxy
@interface YYWeakProxy : NSProxy
@end
// 使用方式:
_link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
代碼很少损姜,有興趣可以自己看下源碼饰剥。
Tips: OC 中 NSProxy
是不繼承自 NSObject
的。
三摧阅、探索環(huán)節(jié):iOS中子線程檢測主線程
在和小伙伴分享這個小工具的時候汰蓉,潘神拋出了這樣一個問題:這里組件是在主線程繪制的label,如果主線程阻塞了還能用嗎棒卷?結(jié)果是不能顾孽。
以下是探索:
3.1、模擬主線程阻塞比规,將 link 放在子線程若厚,發(fā)現(xiàn) timer 不能啟動
// 模擬 主線程阻塞 (不應(yīng)該模擬主線程卡死,模擬卡頓即可)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"即將阻塞");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"同步阻塞主線程");
});
NSLog(@"不會執(zhí)行");
});
3.2蜒什、使用 CFRunLoopAddObserver
檢測主線程是否卡頓:
//將觀察者添加到主線程runloop的common模式下的觀察中
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
這里是能檢測到主線程是否卡頓了盹沈,但是 timer 在子線程中還是跑不起來。
參考 Starming星光社 的 檢測iOS的APP性能的一些方法
3.3吃谣、在子線程手動創(chuàng)建一個 runloop
乞封,提供給 timer。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
// NOTE: 子線程的runloop默認(rèn)不創(chuàng)建岗憋; 在子線程獲取 currentRunLoop 對象的時候肃晚,就會自動創(chuàng)建RunLoop
// 這里不加到 main loop,必須創(chuàng)建一個 runloop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[_link addToRunLoop:runloop forMode:NSRunLoopCommonModes];
// 必須 timer addToRunLoop 后仔戈,再run
[runloop run];
});
這樣关串,就可以在子線程中使用 timer 了拧廊,但是此時只能 log,無法獲通知主線程更新UI: (這里先不在主線程更新UI了)
// 嘗試1:主線程阻塞晋修, 這里就不能獲取到主線程了
// dispatch_async(dispatch_get_main_queue(), ^{
// 阻塞時吧碾,想通過 在主線程更新UI 來查看是不可行了
// label_.text = text;
// });
// 嘗試2:不在主線程操作 UI ,界面會發(fā)生變化
label_.text = text;
參考: 【iOS程序啟動與運轉(zhuǎn)】- RunLoop個人小結(jié)
以上是學(xué)習(xí) YYFPSLabel 時的收獲墓卦,和對于在子線程中檢測主線程的探索倦春。詳情可以戳代碼:YYFPSLabel
四、補充:
@beychen
同學(xué)提問:為什么__weak typeof(self) weakSelf = self; 不能解決 displayLinkWithTarget 循環(huán)引用的問題落剪?
之前給的解釋是:
普通的 VC 持有 block睁本,在 block 外 weakSelf 后再傳入,block 中持有的是 weakSelf 就能正常釋放了忠怖。但是 NSTimer 的 target 傳 weakSelf 卻不行呢堰。由于 NSTimer 是閉源的,猜測原因可能如下:
1凡泣、 NSTimer 在子線程執(zhí)行枉疼,需要線程保活鞋拟,會被加入到 RunLoop 中骂维,被 Runloop 強引用;
2严卖、 Block 和 Timer 席舍,對于 self 的持有方式不同布轿, block 是捕獲了變量哮笆,進行了值拷貝。但是 NSTimer 需要一直調(diào)用 target 的 selector汰扭,如果 target 先于 NSTimer 釋放了稠肘,NSTimer 會調(diào)不到 selector,會崩潰萝毛。所以猜測 NSTimer 的 tagrget 中项阴,對 weakSelf 又進行了類似 StrongSelf 操作,eg:
__weak typeof(self) weakSelf = self;
self.myBlock = ^() {
__strong typeof(weakSelf) strongSelf = weakSelf;
// 永遠不會主動停止的動作
};
3笆包、 由于上述 1环揽,NSTimer 本身不會被釋放(相當(dāng)于一個單例),傳入 NSTimer 的 taget庵佣,被 Timer 加到集合中的話歉胶,即使傳 weakSelf 也不能釋放。這個也是之前 debug 一個內(nèi)存泄露時發(fā)現(xiàn)的類似現(xiàn)象巴粪。
可以參考下: