什麼是FPS
FPS :Frames Per Second 的簡稱縮寫,意思是每秒傳輸幀數,可以理解爲我們常說的“刷新率”(單位爲Hz);FPS是測量用於保存、顯示動態視頻的信息數量。每秒鐘幀數愈多,所顯示的畫面就會愈流暢,fps值越低就越卡頓,所以這個值在一定程度上可以衡量應用在圖像繪製渲染處理時的性能。
目前iOS系統中正常的屏幕刷新率爲60Hz(60次每秒),iOS QuartzCore框架中類CADisplayLink 是一個用於顯示的定時器, 它可以讓用戶程序的顯示與屏幕的硬件刷新保持同步。
如何評測界面的流暢度
一般APP當fps平均在40左右的時候我們就要考慮列表滾動的優化啦
卡頓:tableVIew滑動幀數,也就是30FPS左右(30以下用戶可以明顯感覺到屏幕的卡動,可以稱爲垃圾APP了)
流暢:50-60FPS
CADisplayLink
CADisplayLink是一個能讓我們以和屏幕刷新率相同的頻率將內容畫到屏幕上的定時器。
我們在應用中創建一個新的 CADisplayLink 對象,把它添加到一個runloop中,並給它提供一個 target 和selector 在屏幕刷新的時候調用。
利用此原理,我們可以實時獲取列表頁面的FPS。
CADisplayLink和NSTimer會造成循環引用所有我們可以通過runtime消息轉發機制打破循環引用。
代碼如下:創建WeakProxy類集成NSProxy
//創建WeakProxy類集成NSProxy .h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WeakProxy : NSProxy
+ (instancetype)proxyWith:(id)target;
@end
// .m
//
// WeakProxy.m
// Created by just so so on 2019/9/30.
// Copyright © 2019 bruce yao. All rights reserved.
//
#import "WeakProxy.h"
@interface WeakProxy()
@property (nonatomic, weak) id target;
@end
@implementation WeakProxy
///類方法 初始化
+ (instancetype)proxyWith:(id)target {
WeakProxy *proxy = [WeakProxy alloc];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation getReturnValue:&null];
}
@end
NS_ASSUME_NONNULL_END
創建一個Label的子類用來顯示fps,我們用Swift創建
//
// FPSLabel.swift
// DrawLayer
//
// Created by just so so on 2019/9/30.
// Copyright © 2019 bruce yao. All rights reserved.
//
import UIKit
class FPSLabel: UILabel {
//CADisplayLink
fileprivate var link: CADisplayLink?
fileprivate var count: UInt = 0
fileprivate var lastTime: TimeInterval = 0
override init(frame: CGRect) {
super.init(frame: frame)
///樣式
layer.cornerRadius = 5
layer.masksToBounds = true
backgroundColor = UIColor.init(white: 0, alpha: 0.7)
font = UIFont.init(name: "Menlo", size: 14)
self.textAlignment = .center
///防止循環引用
link = CADisplayLink.init(target: WeakProxy.init(self), selector: #selector(tick(_:)))
///main runloop 添加到
link?.add(to: RunLoop.main, forMode: .common)
}
deinit {
link?.invalidate()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
///滴答滴答
@objc fileprivate func tick(_ link: CADisplayLink) {
if lastTime == 0 {
lastTime = link.timestamp
return
}
count = count + 1
let delta = link.timestamp - lastTime
if delta < 1 {
return
}
lastTime = link.timestamp;
let fps = Double(count) / delta
count = 0
let progress = fps / 60.0
let color = UIColor.init(hue: CGFloat(0.27 * (progress - 0.2)), saturation: 1, brightness: 0.9, alpha: 1)
self.textColor = color
self.text = String.init(format: "%.0lf FPS", round(fps))
}
}
使用
fileprivate var fpsLabel: FPSLabel = FPSLabel.init(frame: CGRect.zero)
///用自動佈局或者直接寫frame在Controller中,填加到View中即可
注意
關於iOS性能優化之屏幕篇,可以參考:https://blog.csdn.net/weixin_38735568/article/details/100893474
關於Timer和CADisplayLink的循環引用原因以及原理詳細介紹,您可以參考:
https://blog.csdn.net/weixin_38735568/article/details/100010612
tableView的其他優化,會不定期更新,敬請期待。