「这是我参与2022初次更文应战的第20天,活动概况检查:2022初次更文应战」。
- 界面优化是一个老生常谈的问题,这篇文章首要介绍
界面烘托
流程,界面卡顿的检测
,处理卡顿
的方法。
1. 界面烘托流程
一般咱们知道视图显现是经过GPU
进行图画烘托加载出来的
-
CPU
核算出要显现的内容,交由GPU
处理。 -
GPU烘托
完结后把烘托的结果交由Frame Buffer
(帧缓存区)处理。 - 帧缓存区会把把数据交给
视频控制器
进行读取 - 经过
数模转化
后显现在屏幕上
1.1 双缓存区设计
为了提高烘托效率
,开发者设计出了双缓存区
,2个FrameBuffer
切换读取,当GPU烘托好放入帧缓存区
,交给视屏控制器读取,之后读取显现过程中GPU烘托
好的结果放在另一个
帧缓存区,这样视频控制器来回切换读取,大大提高了效率
。
但是随之而来的问题是,当视频控制器还未读取完结
时,即屏幕内容刚显现一半
时,GPU
将新的一帧内容提交到帧缓冲区
并把两个缓冲区进行交换
后,视频控制器就会把新的一帧
数据的下半段
显现到屏幕上,形成画面撕裂现象
为了处理这个问题引入了VSync
:笔直同步信号机制
。开启后GPU
会等候屏幕的VSync
宣布信号后进行新一帧的烘托和缓存
。这样防止了加载不完全的问题,但是也会消耗更多的内存
。
1.2 笔直同步信号机制:V-sync
现在主流的移动设备是什么状况呢?从网上查到的资料能够知道,iOS 设备会一直运用双缓存
,并开启笔直同步
。
但是随之而来会有新的问题,界面的卡顿
。咱们知道每一帧的显现是经过CPU核算
好要显现的内容后,交由GPU进行烘托
,之后放入缓存
区,视频控制器读取
显现。如果某一帧CPU
核算时刻过长,或许GPU
烘托过长。加起来的时刻超越
了笔直同步信号的距离时刻
,这个时分视频控制器就不会读取
没有处理好的数据,咱们感觉到卡顿。其实便是掉帧
,如下图所示:
经过图中可知咱们需求对CPU的核算仍是CGPU的烘托进行优化
,削减用户卡顿。
2. 卡顿检测
卡顿的检测一般有2种方法:
1.FPS检测
:咱们能够运用YYKit
中的YYFPSLabel
,也能够模仿它自己自界说一个。
首要是经过CADisplayLink
来完成,经过link
的时刻差核算一秒改写的次数,依据改写的次数显现改写频次。关于一般的检测FPS现已够用了。
- 主线程卡顿监控
咱们经过RunLoop
来监控,因为卡顿的是业务,而业务是交由主线程
的RunLoop
处理的。
@interface LGBlockMonitor (){
CFRunLoopActivity activity;
}
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@property (nonatomic, assign) NSUInteger timeoutCount;
@end
@implementation LGBlockMonitor
+ (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
经过runloop
监测主线程每次履行使命循环
的时刻,如果这个时刻超越咱们界说的时刻说明发生了超时
,出行了卡顿。首先给mainRunloop
增加观察者,经过CFRunLoopAddObserver
,每次结束使命循环都会调用回调函数
。之后再子线程
中监控时长
,这里采用信号量
核算距离。当距离超越1
秒的话,而且两种状态kCFRunLoopBeforeSources
、kCFRunLoopAfterWaiting
,则说明发生了卡顿
。
咱们把线程睡觉2秒,监听超越1秒所以报卡顿了。
一秒左右的衡量标准 很大可能性接连来 防止大规模打印!,说明3秒了卡顿2次,减去默认1秒。
不卡顿的状况。 也能够直接运用三方库
-
Swift
的卡顿检测第三方ANREye,其首要思路是:创立子线程进行循环监测,每次检测时设置符号置为true
,然后派发使命到主线程,符号置为false
,接着子线程睡觉超越阈值
时,判断符号是否为false
,如果没有,说明主线程发生了卡顿 -
OC
能够运用 微信matrix、滴滴DoraemonKit
3. 界面优化
3.1 CPU方面优化
-
预排版
:关于咱们常用的比方ableview,咱们能够在拿到数据的时分就核算好它的布局状况
,防止烘托的时分进行核算。比方cell的行高
等。 - 削减
动态的增加
view,比方cell增加view。 - 关于一些
没有交互
的显现是图能够用CALayer
代替UIView
,用轻量级的目标
- 削减
Autolayout
的运用,关于简略的布局Autolayout
能够许多节约咱们时刻,关于杂乱页面,嵌套布局较多会形成CPU运算
消耗会呈指数级上升
如果你不想手动调整frame
等特点,也能够凭借三方库,例如Masonry(OC)、SnapKit(Swift)、ComponentKit、AsyncDisplayKit等
-
按需加载
,咱们能够运用懒加载
和复用机制
。不要一次性创立一切的subview
,当需求时才创立,当完结使命,进行切换的时分能够放入一个可重用
的队列,这样下次滚动或许显现的时分,防止不必要的内存分配。 - 当有许多目标开释时,也是十分耗时的,尽量挪到后台线程去开释
- 关于一些处理
较慢的objects
,比方NSDateFormatter
和NSCalendar
。比方请求的数据中显现日期,想要防止运用这个目标的瓶颈就需求复用他们,能够经过增加特点
到类中,或许创立静态变量
来完成。 - 尽量
防止运用通明view
,因为运用通明view,会导致在GPU中核算像素时,会将通明view下层图层的像素也核算进来 - 请求或许耗时操作异脚步线程处理,
不要堵塞主线程
。 - 正确的
设置背景图片
,全屏背景图的话增加一个UIImageview
作为子View。某个小小的view
的背景图,运用UIColor
的colorWithPatternImage
来做,它会更快的烘托不会糟蹋许多内存。 - 尽量运用
PNG
图片,不运用JPGE
图片;优化图片大小,尽量防止动态缩放
,在运行中缩放图片很消耗资源,特别UIImageview
嵌套UIScrollView
中。如果是从服务器中下载的,能够在下载前调整到适宜大小。也能够在下载完结后用background thread
进行缩放一次,之后UIImageview
运用缩放后的图片。 - 图片在运用
UIImage
或许CGImageSource
创立时,图片不会当即解码,而是在设置的时分进行解码,咱们能够在子线程中先将图片制作到CGBitmapContext
,然后从Bitmap
直接创立图片,例如SDWebImage
三方结构中对图片编解码的处理。这便是Image的预解码
- 当运用CG最初的方法制作图画到画布中,然后从画布中创立图片时,能够将图画的
制作
在子线程
中进行 - 图片是否进行缓存,
imageNamed
会加载成功后缓存到内存中,而关于imageWithContentOfFile
则不会,实用于加载一张大图而且运用一次。
3.2 GPU方面
关于GPU方面首要是优化它烘托进行优化
-
尽量
削减在短时刻内许多图片的显现
,尽可能将多张图片合为一张显现
,首要是因为当有许多图片进行显现时,无论是CPU的核算仍是GPU的烘托,都是十分耗时的,很可能呈现掉帧的状况 -
尽量
防止离屏烘托
,它会开辟新的缓存区
,同时整个过程会多次切换上下文
,显现从当时的屏幕切换到离屏,离屏烘托结束后把离屏缓冲区的结果显现到屏幕上,又要将上下文环境从离屏切换到当时屏幕。这样会形成许多内存糟蹋,更多的开支。 -
离屏烘托一般有:
光栅化
layer.shouldRasterize = YES;遮罩层
mask,阴影
shadow,圆角
cornerRadius+clipsToBounds,毛玻璃
效果等。 -
异步烘托
,例如能够将cell中的一切控件、视图合成一张图片
进行显现,能够参阅Graver三方结构。