「这是我参与2022初次更文应战的第20天,活动概况检查:2022初次更文应战」。

  • 界面优化是一个老生常谈的问题,这篇文章首要介绍界面烘托流程,界面卡顿的检测处理卡顿的方法。

1. 界面烘托流程

一般咱们知道视图显现是经过GPU进行图画烘托加载出来的

iOS 中的界面优化

  1. CPU核算出要显现的内容,交由GPU处理。
  2. GPU烘托完结后把烘托的结果交由Frame Buffer(帧缓存区)处理。
  3. 帧缓存区会把把数据交给视频控制器进行读取
  4. 经过数模转化后显现在屏幕上

1.1 双缓存区设计

为了提高烘托效率,开发者设计出了双缓存区,2个FrameBuffer切换读取,当GPU烘托好放入帧缓存区,交给视屏控制器读取,之后读取显现过程中GPU烘托好的结果放在另一个帧缓存区,这样视频控制器来回切换读取,大大提高了效率

iOS 中的界面优化

但是随之而来的问题是,当视频控制器还未读取完结时,即屏幕内容刚显现一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显现到屏幕上,形成画面撕裂现象

iOS 中的界面优化

为了处理这个问题引入了VSync笔直同步信号机制。开启后GPU会等候屏幕的VSync宣布信号后进行新一帧的烘托和缓存。这样防止了加载不完全的问题,但是也会消耗更多的内存

1.2 笔直同步信号机制:V-sync

现在主流的移动设备是什么状况呢?从网上查到的资料能够知道,iOS 设备会一直运用双缓存,并开启笔直同步。 但是随之而来会有新的问题,界面的卡顿。咱们知道每一帧的显现是经过CPU核算好要显现的内容后,交由GPU进行烘托,之后放入缓存区,视频控制器读取显现。如果某一帧CPU核算时刻过长,或许GPU烘托过长。加起来的时刻超越了笔直同步信号的距离时刻,这个时分视频控制器就不会读取没有处理好的数据,咱们感觉到卡顿。其实便是掉帧,如下图所示:

iOS 中的界面优化

经过图中可知咱们需求对CPU的核算仍是CGPU的烘托进行优化,削减用户卡顿。

2. 卡顿检测

卡顿的检测一般有2种方法:
1.FPS检测:咱们能够运用YYKit中的YYFPSLabel,也能够模仿它自己自界说一个。

iOS 中的界面优化

首要是经过CADisplayLink来完成,经过link的时刻差核算一秒改写的次数,依据改写的次数显现改写频次。关于一般的检测FPS现已够用了。

  1. 主线程卡顿监控
    咱们经过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秒的话,而且两种状态kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting,则说明发生了卡顿

iOS 中的界面优化
咱们把线程睡觉2秒,监听超越1秒所以报卡顿了。

iOS 中的界面优化

一秒左右的衡量标准 很大可能性接连来 防止大规模打印!,说明3秒了卡顿2次,减去默认1秒。

iOS 中的界面优化

不卡顿的状况。 也能够直接运用三方库

  • Swift的卡顿检测第三方ANREye,其首要思路是:创立子线程进行循环监测,每次检测时设置符号置为true,然后派发使命到主线程,符号置为false,接着子线程睡觉超越阈值时,判断符号是否为false,如果没有,说明主线程发生了卡顿
  • OC能够运用 微信matrix、滴滴DoraemonKit

3. 界面优化

3.1 CPU方面优化

  1. 预排版:关于咱们常用的比方ableview,咱们能够在拿到数据的时分就核算好它的布局状况,防止烘托的时分进行核算。比方cell的行高等。
  2. 削减动态的增加view,比方cell增加view。
  3. 关于一些没有交互的显现是图能够用CALayer代替UIView,用轻量级的目标
  4. 削减Autolayout的运用,关于简略的布局Autolayout能够许多节约咱们时刻,关于杂乱页面,嵌套布局较多会形成CPU运算消耗会呈指数级上升如果你不想手动调整 frame 等特点,也能够凭借三方库,例如Masonry(OC)、SnapKit(Swift)、ComponentKit、AsyncDisplayKit等
  5. 按需加载,咱们能够运用懒加载复用机制。不要一次性创立一切的subview,当需求时才创立,当完结使命,进行切换的时分能够放入一个可重用的队列,这样下次滚动或许显现的时分,防止不必要的内存分配。
  6. 当有许多目标开释时,也是十分耗时的,尽量挪到后台线程去开释
  7. 关于一些处理较慢的objects,比方NSDateFormatterNSCalendar。比方请求的数据中显现日期,想要防止运用这个目标的瓶颈就需求复用他们,能够经过增加特点到类中,或许创立静态变量来完成。
  8. 尽量防止运用通明view,因为运用通明view,会导致在GPU中核算像素时,会将通明view下层图层的像素也核算进来
  9. 请求或许耗时操作异脚步线程处理,不要堵塞主线程
  10. 正确的设置背景图片,全屏背景图的话增加一个UIImageview作为子View。某个小小的view的背景图,运用UIColorcolorWithPatternImage来做,它会更快的烘托不会糟蹋许多内存。
  11. 尽量运用PNG图片,不运用JPGE图片;优化图片大小,尽量防止动态缩放,在运行中缩放图片很消耗资源,特别UIImageview嵌套UIScrollView中。如果是从服务器中下载的,能够在下载前调整到适宜大小。也能够在下载完结后用background thread进行缩放一次,之后UIImageview运用缩放后的图片。
  12. 图片在运用UIImage或许CGImageSource创立时,图片不会当即解码,而是在设置的时分进行解码,咱们能够在子线程中先将图片制作到CGBitmapContext,然后从Bitmap直接创立图片,例如SDWebImage三方结构中对图片编解码的处理。这便是Image的预解码
  13. 当运用CG最初的方法制作图画到画布中,然后从画布中创立图片时,能够将图画的制作子线程中进行
  14. 图片是否进行缓存,imageNamed会加载成功后缓存到内存中,而关于imageWithContentOfFile则不会,实用于加载一张大图而且运用一次。

3.2 GPU方面

关于GPU方面首要是优化它烘托进行优化

  1. 尽量削减在短时刻内许多图片的显现,尽可能将多张图片合为一张显现,首要是因为当有许多图片进行显现时,无论是CPU的核算仍是GPU的烘托,都是十分耗时的,很可能呈现掉帧的状况

  2. 尽量防止离屏烘托,它会开辟新的缓存区,同时整个过程会多次切换上下文,显现从当时的屏幕切换到离屏,离屏烘托结束后把离屏缓冲区的结果显现到屏幕上,又要将上下文环境从离屏切换到当时屏幕。这样会形成许多内存糟蹋,更多的开支。

  3. 离屏烘托一般有:光栅化 layer.shouldRasterize = YES;遮罩层mask,阴影shadow, 圆角cornerRadius+clipsToBounds,毛玻璃效果等。

  4. 异步烘托,例如能够将cell中的一切控件、视图合成一张图片进行显现,能够参阅Graver三方结构。