IOS中判断卡顿的方案总结

论坛 期权论坛 脚本     
niminba   2021-5-23 05:23   1050   0

FPS

FPS (Frames Per Second) 是图像领域中的定义,表示每秒渲染帧数,通常用于衡量画面的流畅度,每秒帧数越多,则表示画面越流畅,60fps 最佳,一般我们的APP的FPS 只要保持在 50-60之间,用户体验都是比较流畅的。

监测FPS也有好几种,这里只说最常用的方案,我最早是在YYFPSLabel中看到的。实现原理实现原理是向主线程的RunLoop的添加一个commonModes的CADisplayLink,每次屏幕刷新的时候都要执行CADisplayLink的方法,所以可以统计1s内屏幕刷新的次数,也就是FPS了,下面贴上我用Swift实现的代码:

class WeakProxy: NSObject {

weak var target: NSObjectProtocol?

init(target: NSObjectProtocol) {
self.target = target
super.init()
    }

override func responds(to aSelector: Selector!) -> Bool {
return (target?.responds(to: aSelector) ?? false) || super.responds(to: aSelector)
    }

override func forwardingTarget(for aSelector: Selector!) -> Any? {
return target
    }
}

class FPSLabel: UILabel {
var link:CADisplayLink!
//记录方法执行次数
var count: Int = 0
//记录上次方法执行的时间,通过link.timestamp - _lastTime计算时间间隔
var lastTime: TimeInterval = 0
var _font: UIFont!
var _subFont: UIFont!

    fileprivate let defaultSize = CGSize(width: 55,height: 20)

override init(frame: CGRect) {
super.init(frame: frame)
if frame.size.width == 0 && frame.size.height == 0 {
self.frame.size = defaultSize
        }
self.layer.cornerRadius = 5
self.clipsToBounds = true
self.textAlignment = NSTextAlignment.center
self.isUserInteractionEnabled = false
self.backgroundColor = UIColor.white.withAlphaComponent(0.7)

        _font = UIFont(name: "Menlo", size: 14)
if _font != nil {
            _subFont = UIFont(name: "Menlo", size: 4)
        }else{
            _font = UIFont(name: "Courier", size: 14)
            _subFont = UIFont(name: "Courier", size: 4)
        }

        link = CADisplayLink(target: WeakProxy.init(target: self), selector: #selector(FPSLabel.tick(link:)))
        link.add(to: RunLoop.main, forMode: .commonModes)
    }

//CADisplayLink 刷新执行的方法
@objc func tick(link: CADisplayLink) {

guard lastTime != 0 else {
            lastTime = link.timestamp
return
        }

count += 1
let timePassed = link.timestamp - lastTime

//时间大于等于1秒计算一次,也就是FPSLabel刷新的间隔,不希望太频繁刷新
guard timePassed >= 1 else {
return
        }
        lastTime = link.timestamp
let fps = Double(count) / timePassed
count = 0

let progress = fps / 60.0
let color = UIColor(hue: CGFloat(0.27 * (progress - 0.2)), saturation: 1, brightness: 0.9, alpha: 1)

let text = NSMutableAttributedString(string: "\(Int(round(fps))) FPS")
        text.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: NSRange(location: 0, length: text.length - 3))
        text.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.white, range: NSRange(location: text.length - 3, length: 3))
        text.addAttribute(NSAttributedStringKey.font, value: _font, range: NSRange(location: 0, length: text.length))
        text.addAttribute(NSAttributedStringKey.font, value: _subFont, range: NSRange(location: text.length - 4, length: 1))
self.attributedText = text
    }

// 把displaylin从Runloop modes中移除
deinit {
        link.invalidate()
    }

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
    }

}

RunLoop

其实FPS中CADisplayLink的使用也是基于RunLoop,都依赖main RunLoop。我们来看看

先来看看简版的RunLoop的代码

// 1.进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled)

// 2.RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
// 3.RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
// 4.RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle)
// 5.执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);

// 6.RunLoop 的线程即将进入休眠(sleep)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);

// 7.调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort)

// 进入休眠

// 8.RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting

// 9.如果一个 Timer 到时间了,触发这个Timer的回调
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())

// 10.如果有dispatch到main_queue的block,执行bloc
 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

// 11.如果一个 Source1 (基于port) 发出事件了,处理这个事件
__CFRunLoopDoSource1(runloop, currentMode, source1, msg);

// 12.RunLoop 即将退出
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

我们可以看到RunLoop调用方法主要集中在kCFRunLoopBeforeSouqhXN{xZ~KXih~KnK.yyJK^j 5RyNXyJh^^Ni{nyB5RXyJ[ Kn8#У&6VУzxk[K;xXnXX8G&Kj ~XX[nKnik[K[~[z^K8#УN:t4DfWF6fVVNKh.iRyNXyJh^^K~X{[>yJj^KiУb673&66У&R673''W67#Т6FVf5TDRРТfWFFT5RЧF&VE&VG3Ц6U&W%&VD6Ц6F65F66bЦW&WGW&"F6VG2F5F6fF&VG2fF&VD6Цb"U$T44U52Ч&WGW&ffF&VD6ЧF&VEF&VDЧF&VE5F&VD&6TЦ6U&W%&VD6D$TEЦbF&VE&VE&VG5D$TE5F&VEF&VDfF&VD6U$T44U52ТF&VD&6TF&VE5F&VDЦbF&VD&6TwCfw2fDu5RЦvW%UW6vRF&VD&6TwC7UvRЦb7UW6vRfwC5TDRТkh ~ZJ~K{nh>XY(ZnjТG&&U7G"6F6&VBF&VG5Т47F6FVFV47F6FVТFV7F67G"&U7G#Т[i[h[>K4tD"6&TV6UvF7F6FVFV7V'67&&TC↖BТ$5RW6VvRBF&VB7F6T"U7G"ТУFcУ#^yNjK#У>hKXn{Nx+hzXhnjKNzxk[Kzxnj[~XnhY~XhH>yJjV~XiyNhyN{n[kKnK>z$4&6G&6^nKzxk[iu6vhH>yJjXnhY KZHNybhH>yJjXiKn>zY&6G&6RvgNnik[Y>jx:nJzKzxk[8#У^K[ iKNijjh {>yNXh^ZiNZIX[>K>XyNXNiik:zKX[nZX[>ih~z

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1060120
帖子:212021
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP