• 卡顿检测 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( 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

  • 相关阅读:
    网站访问量大 怎样优化mysql数据库
    BootStrap 模态框禁用空白处点击关闭
    常用SQL语句
    诅咒JavaScript之----ArcGIS JavaScript 点聚合 ClusterLayer
    模态框与 天地图地图控件冲突
    FXK Javascript
    从列表中或数组中随机抽取固定数量的元素组成新的数组或列表
    wangEditor
    手把手教你用vue-cli构建一个简单的路由应用
    解决eclipse端口被占用的问题
  • 原文地址:https://www.cnblogs.com/huangzs/p/16156609.html
Copyright © 2020-2023  润新知