卡顿检测 iOS
FPS监控
:因为iOS设备屏幕的刷新时间是60次/秒
,一次刷新就是一次VSync信号,时间间隔是1000ms/60 = 16.67ms
,所有如果咋16.67ms内下一帧数据没有准备好,就会产生掉帧RunLoop监控
:通过子线程检测主线程的RunLoop的状态,kCFRunLoopBeforeSources
和kCFRunLoopAfterWaiting
两个状态之间的耗时是否达到一定的阈值
FPS监控
参照YYKit
中的YYFPSLabel
,其中通过CADisplayLink
来实现,通过刷新次数/时间差
得到刷新频率
class YPFPSLabel: UILabel { fileprivate var 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 override init(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
在北京的灯中,有一盏是我家的。这个梦何时可以实现?哪怕微微亮。北京就像魔鬼训练营,有能力的留,没能力的走……