1. 程式人生 > 其它 >卡頓檢測 iOS

卡頓檢測 iOS

  • FPS監控:因為iOS裝置螢幕的重新整理時間是60次/秒,一次重新整理就是一次VSync訊號,時間間隔是1000ms/60 = 16.67ms,所有如果咋16.67ms內下一幀資料沒有準備好,就會產生掉幀
  • RunLoop監控:通過子執行緒檢測主執行緒的RunLoop的狀態,kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting兩個狀態之間的耗時是否達到一定的閾值

 

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

開闢子執行緒,通過監聽主執行緒的kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting兩個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