卡頓檢測 iOS
阿新 • • 發佈:2022-04-17
-
FPS監控
:因為iOS裝置螢幕的重新整理時間是60次/秒
,一次重新整理就是一次VSync訊號,時間間隔是1000ms/60 = 16.67ms
,所有如果咋16.67ms內下一幀資料沒有準備好,就會產生掉幀 -
RunLoop監控
:通過子執行緒檢測主執行緒的RunLoop的狀態,kCFRunLoopBeforeSources
和kCFRunLoopAfterWaiting
兩個狀態之間的耗時是否達到一定的閾值
FPS監控
參照YYKit
中的YYFPSLabel
,其中通過CADisplayLink
來實現,通過重新整理次數/時間差
得到重新整理頻率
class YPFPSLabel: UILabel { fileprivatevar link: CADisplayLink = { let link = CADisplayLink.init() return link }() fileprivate var count: Int = 0 fileprivate var lastTime: TimeInterval = 0.0 fileprivate var fpsColor: UIColor = { return UIColor.green }() fileprivate var fps: Double = 0.0 overrideinit(frame: CGRect) { var f = frame if f.size == CGSize.zero { f.size = CGSize(width: 80.0, height: 22.0) } super.init(frame: f) self.textColor = UIColor.white self.textAlignment = .center self.font = UIFont.init(name: "Menlo", size: 12) self.backgroundColor = UIColor.lightGray //通過虛擬類 link = CADisplayLink.init(target: CJLWeakProxy(target:self), selector: #selector(tick(_:))) link.add(to: RunLoop.current, forMode: RunLoop.Mode.common) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { link.invalidate() } @objc func tick(_ link: CADisplayLink){ guard lastTime != 0 else { lastTime = link.timestamp return } count += 1 //時間差 let detla = link.timestamp - lastTime guard detla >= 1.0 else { return } lastTime = link.timestamp //重新整理次數 / 時間差 = 重新整理頻次 fps = Double(count) / detla let fpsText = "\(String.init(format: "%.2f", fps)) FPS" count = 0 let attrMStr = NSMutableAttributedString(attributedString: NSAttributedString(string: fpsText)) if fps > 55.0 { //流暢 fpsColor = UIColor.green }else if (fps >= 50.0 && fps <= 55.0){ //一般 fpsColor = UIColor.yellow }else{ //卡頓 fpsColor = UIColor.red } attrMStr.setAttributes([NSAttributedString.Key.foregroundColor: fpsColor], range: NSMakeRange(0, attrMStr.length - 3)) attrMStr.setAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], range: NSMakeRange(attrMStr.length - 3, 3)) DispatchQueue.main.async { self.attributedText = attrMStr } } }
RunLoop監控
參考 微信的matrix,滴滴的DoraemonKit
開闢子執行緒,通過監聽主執行緒的kCFRunLoopBeforeSources
和kCFRunLoopAfterWaiting
兩個Activity之間的差值
#import "YPBlockMonitor.h" @interface YPBlockMonitor (){ CFRunLoopActivity activity; } @property (nonatomic, strong) dispatch_semaphore_t semaphore; @property (nonatomic, assign) NSUInteger timeoutCount; @end @implementation YPBlockMonitor + (instancetype)sharedInstance { static id instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } - (void)start{ [self registerObserver]; [self startMonitor]; } static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { LGBlockMonitor *monitor = (__bridge LGBlockMonitor *)info; monitor->activity = activity; // 傳送訊號 dispatch_semaphore_t semaphore = monitor->_semaphore; dispatch_semaphore_signal(semaphore); } - (void)registerObserver{ CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; //NSIntegerMax : 優先順序最小 CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, NSIntegerMax, &CallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); } - (void)startMonitor{ // 建立訊號 _semaphore = dispatch_semaphore_create(0); // 在子執行緒監控時長 dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (YES) { // 超時時間是 1 秒,沒有等到訊號量,st 就不等於 0, RunLoop 所有的任務 long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC)); if (st != 0) { if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting) { if (++self->_timeoutCount < 2){ NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount); continue; } // 一秒左右的衡量尺度 很大可能性連續來 避免大規模列印! NSLog(@"檢測到超過兩次連續卡頓"); } } self->_timeoutCount = 0; } }); } @end
轉自:https://www.jianshu.com/p/2f9a06932879