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^j5RyNXyJh^^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 |