iOS系统已顺滑不卡顿闻名,作为开发者我们来看怎么检测你的App的FPS的。众所周知的是NSTimer是定时计数的霸主,然鹅Timer是不准确的。纳尼?哈哈不用紧张,这个不准确是相对来说的,在这个多一秒不多,少一秒不少的世界你还是用Timer吧
我们先来看看官方文档怎么描述CADisplayLink:
A timer object that allows your application to synchronize its drawing to the refresh rate of the display.
我们来看个例子:
class Test{
func createDisplayLink(){
let dispalylink : CADisplayLink = CADisplayLink(target: self, selector: #selector(step))
dispalylink.add(to: RunLoop.current, forMode: RunLoop.Mode.default)
}
@objc func step(displaylink: CADisplayLink){
let actualFramesPerSecond = 1 / (displaylink.targetTimestamp - displaylink.timestamp)
print(actualFramesPerSecond)
}
}
这其中简单展示了其使用。从打印的结果我们能看到帧率是59.9999…也就是常说的60
下面我们看看怎么来完成一个在Label上显示的粒子
typealias Label = UILabel | |
---|---|
class XSFps: Label { | |
private var link: CADisplayLink? | |
private let defaultFPS = 60.0 | |
private var count: TimeInterval = 0 | |
private var lastTime:TimeInterval = 0 | |
private var lastSecondOfFrameTimes:[TimeInterval] = [] | |
private var frameNumber:UInt = 0 | |
private var frameDuration = 1.0 | |
override var text: String?{ | |
didSet{ | |
textAlignment = .center | |
font = UIFont.boldSystemFont(ofSize: 13) | |
self.frame.size = CGSize(width: 25, height: 15) | |
textColor = UIColor.red | |
} | |
} | |
@objc func toggleDrage(sender:UIPanGestureRecognizer){ | |
let view = sender.view | |
let newPoint = sender.translation(in: view) | |
let x:CGFloat = (sender.view?.center.x)! + newPoint.x | |
let y: CGFloat = (sender.view?.center.y)! + newPoint.y | |
sender.view?.center = CGPoint(x: x, y: y) | |
sender.setTranslation(.zero, in: view?.superview) | |
} | |
deinit { | |
if (link != nil) { | |
#if swift(>=4.2) | |
link?.remove(from: RunLoop.main, forMode: RunLoop.Mode.common) | |
#else | |
link?.remove(from: RunLoop.main, forMode: RunLoopMode.commonModes) | |
#endif | |
} | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
for _ in 0 ... Int(defaultFPS) { | |
lastSecondOfFrameTimes.append(0) | |
} | |
addPan() | |
link = CADisplayLink(target: self, selector: #selector(start(aLink:))) | |
#if swift(>=4.2) | |
link?.add(to: RunLoop.main, forMode: RunLoop.Mode.common) | |
#else | |
link?.add(to: RunLoop.main | |
, forMode: RunLoopMode.commonModes) | |
link?.add(to: RunLoop.main | |
, forMode: RunLoopMode.commonModes) | |
#endif | |
} | |
private func addPan(){ | |
let pan = UIPanGestureRecognizer(target: self, action: #selector(toggleDrage(sender:))) | |
self.isUserInteractionEnabled = true | |
self.addGestureRecognizer(pan) | |
} | |
override func removeFromSuperview() { | |
link?.invalidate() | |
super.removeFromSuperview() | |
} | |
@objc func start(aLink:CADisplayLink){ | |
frameNumber += 1 | |
let currentFrameIndex = Int(frameNumber) % Int(defaultFPS) | |
lastSecondOfFrameTimes[currentFrameIndex] = (link?.timestamp)! | |
var droppedFrameCount = 0 | |
let lastFrameTime = CACurrentMediaTime() - frameDuration | |
for i in 0..<Int(defaultFPS) { | |
if (1.0 <= lastFrameTime - lastSecondOfFrameTimes[i]) { | |
droppedFrameCount += 1 | |
} | |
} | |
let currentFPS = Int(defaultFPS) - droppedFrameCount | |
#if (arch(i386) || arch(x86_64)) && debug | |
text = "\(aLink.timestamp)\n \(aLink.duration) \n\(aLink.framesPerSecond) \n \(currentFPS)" | |
#else | |
text = "\(currentFPS)" | |
#endif | |
} | |
required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
for _ in 0 ... Int(defaultFPS) { | |
lastSecondOfFrameTimes.append(0) | |
} | |
addPan() | |
link = CADisplayLink(target: self, selector: #selector(start(aLink:))) | |
#if swift(>=4.2) | |
link?.add(to: RunLoop.main, forMode: RunLoop.Mode.common) | |
#else | |
link?.add(to: RunLoop.main | |
, forMode: RunLoopMode.commonModes) | |
#endif | |
} | |
} |