iOS程序中,视图能够用UIViewCALayer来创立,下面就以它们为导火索来进一步学习总结iOS程序显现功能优化。

一、UIView和CALayer的差异

  1. UIViewUIKit结构中的, 承继于UIRespond,能够呼应接触事情;CALayerQuartzCore结构里边CoreAnimation中的,承继自NSObject, 不呼应事情。
  2. UIView有个只读的layer特点,称为根Layer,UIView的图层是由layer来生成的,对UIView的frame等特点的修改也相当所以对layer的特点的修改。
  3. 咱们能够给UIView 增加子layer,self.layer addSublayer:,作用像addSubview相同显现,但sublayer在修改positionsizeopacity等可动画特点时会产生隐式动画(默许有个动画作用)。
  4. 运用体系供给的layer和自定义layer都可增加为subLayer,但需求调用[layer setNeedsDisplay]; UIView增加子视图不需求手动调用setNeedsDisplay。
  5. UIView根layer的署理。

二、自定义layer

这里介绍两种办法:设置layer署理、自定义CALayer子类。

    /// 办法一:增加layer,设置署理,调用 [layer setNeedsDisplay];
    CALayer *layer = [CALayer layer];// 测验设置署理办法用这个
    // ZLLayer *layer = [ZLLayer layer]; // 测验自定义layer用这个
    layer.delegate = self;
    layer.bounds = CGRectMake(0, 0, 200, 200);
    layer.backgroundColor = UIColor.blueColor.CGColor;
    layer.position = CGPointMake(100, 100);
    layer.anchorPoint = CGPointZero;
    [self.view.layer addSublayer:layer];
    [layer setNeedsDisplay];
// 署理办法的完结
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    NSLog(@"%s", __func__);
    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
    CGContextAddEllipseInRect(ctx, CGRectMake(10, 10, 80, 80));
    CGContextFillPath(ctx);
}
/// 办法二:自定义子类(自定义的ZLLayer能够直接增加到layer上,也能够创立自定义view类,在+ (Class)layerClass中返回)
@implementation ZLLayer
- (void)display{
    NSLog(@"%s", __func__);
    [super display];
    NSLog(@"%@", self.contents);
}
- (void)drawInContext:(CGContextRef)ctx{
    NSLog(@"%s", __func__);
    CGContextAddEllipseInRect(ctx, CGRectMake(10, 10, 80, 80));
    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
    CGContextFillPath(ctx);
}
@end

三、UIView从创立到显现主要经过了哪些过程?

咱们经过上述自定义的ZLLayer类, 在各个办法中打印调用办法顺序及上下文,能够验证下面的过程。

  1. 当视图 (UIView或CALayer) 榜首次展现到窗口或许手动调用-setNeedsDisplay时,当时图层Layer会被标记为 dirty(屡次调用 -setNeedsDisplay作用是相同的), 接着当烘托体系准备好时会调用图层layer的-display办法。

  2. -display办法中, [super dispalay]中的完结是:

  • 首先会创立上下文*(假如已有上下文则运用之前的)*.

  • 接下来会判断layer的delegate有没有完结这个办法- (void)displayLayer:(CALayer *)layer;假如完结了,那么体系会以为layer.content是由这个办法中生成,会调用这个办法,而且不会去制作内容。不会去制作内容的意思是:即不会调用layer内部- (void)drawInContext:(CGContextRef)ctx, 也不会调用署理的- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx

  • 假如layer的delegate没有完结- (void)displayLayer:(CALayer *)layer, 则体系会以为需求手动制作内容,首先判断Layer中有没有完结- (void)drawInContext:(CGContextRef)ctx办法,完结了则调用; 不然调用署理的- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx去完结内容制作。

  1. UIView是根Layer的署理,UIView中完结了署理办法- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx,内部会调用UIView的- (void)drawRect:(CGRect)rect 办法。
  2. 上面办法调用完后,GPU依据上下文环境进行纹路的烘托,烘托完结后放入帧缓存中。
  3. 当显现器发出VSync(垂直同步信号)时,视频控制器从帧缓存中读取一帧,按照水平同步信号显现到屏幕。 APP卡顿优化学习总结

综上,咱们创立视图应该是这样的:

  • 首先关于不杂乱的视图内容,则正常运用UIView、layer。
  • 假如像八角形、股票曲线等杂乱内容,需求手动运用UIKit中的UIBezierPath等API或许Core Graphics中的API的,能够依据需求选择
    • 榜首种办法: 创立CALayer,设置delegate,然后在署理类完结办法- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx,将内容制作到这个ctx中。
    • 第二种办法:创立自定义CALayer子类,重写办法- (void)drawInContext:(CGContextRef)ctx,将内容制作到这个ctx中。
    • 第三种办法,将自定义view作为展现视图,重写view的- (void)drawRect:(CGRect)rect 办法,将内容制作到这个ctx中。
    • 第四种办法:运用UIKit中的API,里边会默许获取在栈顶的上下文,将内容制作到上下文中。

比方制作一个八角形的代码:

  • 经过 UIKit 能做到这一点
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(16.72, 7.22)];
[path addLineToPoint:CGPointMake(3.29, 20.83)];
[path addLineToPoint:CGPointMake(0.4, 18.05)];
[path addLineToPoint:CGPointMake(18.8, -0.47)];
[path addLineToPoint:CGPointMake(37.21, 18.05)];
[path addLineToPoint:CGPointMake(34.31, 20.83)];
[path addLineToPoint:CGPointMake(20.88, 7.22)];
[path addLineToPoint:CGPointMake(20.88, 42.18)];
[path addLineToPoint:CGPointMake(16.72, 42.18)];
[path addLineToPoint:CGPointMake(16.72, 7.22)];
[path closePath];
path.lineWidth = 1;
[[UIColor redColor] setStroke];
[path stroke];
  • 经过 Core Graphics, 需求手动获取上下文
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 16.72, 7.22);
CGContextAddLineToPoint(ctx, 3.29, 20.83);
CGContextAddLineToPoint(ctx, 0.4, 18.05);
CGContextAddLineToPoint(ctx, 18.8, -0.47);
CGContextAddLineToPoint(ctx, 37.21, 18.05);
CGContextAddLineToPoint(ctx, 34.31, 20.83);
CGContextAddLineToPoint(ctx, 20.88, 7.22);
CGContextAddLineToPoint(ctx, 20.88, 42.18);
CGContextAddLineToPoint(ctx, 16.72, 42.18);
CGContextAddLineToPoint(ctx, 16.72, 7.22);
CGContextClosePath(ctx);
CGContextSetLineWidth(ctx, 1);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokePath(ctx);

四、隐式动画

咱们对UIView中非根layer修改特点如:posion、bounds、opacity时会自动有一个动画作用,假如不想要这个作用能够这么做:

// 运用UIView的api去除隐式动画,假如不是手动增加的layer可起作用
[UIView performWithoutAnimation:^{
    [collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];
}];
// 假如self.frameLayer是手动增加到view中的,运用下面的办法去除隐式动画
- (void)layoutSubviews{
    [super layoutSubviews];
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.frameLayer.frame = self.frameView.bounds;
    [CATransaction commit];
}

隐式动画产生的原因:

  • Core Animation在每个RunLoop周期会自动开始一次新的业务CATranscation,即使你不显式的运用[CATranscation begin]开始一次业务。
  • 在一次RunLoop运转循环中改动的特点会被会集履行默许0.25秒的动画。
  • 业务经过CATransaction类来做办理的
  • 详细了解也可阅览iOS动画-CALayer隐式动画原理与特性
//1.动画特点的入栈
+ (void)begin;
//2.动画特点出栈
+ (void)commit;
//3.设置当时业务的动画时刻
+ (void)setAnimationDuration:(CFTimeInterval)dur;
//4.在动画结束时供给一个完结的动作
+ (void)setCompletionBlock:(nullable void (^)(void))block;

五、进步显现功能

iOS UIView和CALayer

  1. GPU 是一个专门为图形高并发核算而量身定做的处理单元, 能高效的将不同纹路组成起来;
  2. GPU Driver 使不同的GPU鄙人一个层级(如 OpenGL/OpenGL ES)更为统一;
  3. OpenGL 和 GPU 亲近的作业以进步GPU的能力,并完结硬件加速烘托
  4. iOS程序中,UIKit结构中控件的图层部分是由CALayer来完结,而CALayer是Core Animation中的。
  5. 一个纹路就是一个包含 RGBA 值的长方形,在 Core Animation 国际中这就相当于一个 CALayer。

1. 从GPU作业,能够做的优化:

每一个 layer 是一个纹路,一切的纹路都以某种办法堆叠在互相的顶部,GPU 需求组成堆叠的纹路来得到屏幕上像素对应详细的 RGB 值。GPU核算时分像素和纹路对齐、不对齐两种情况:

  • 在像素对齐的情况下,假如纹路不透明,那么GPU会取最上层的那个纹路的色值;假如是有透明度,则需求核算多个纹路共同作用下的色值。
  • 在像素不对齐的情况下,主要有两个原因或许会形成不对齐。
    • 榜首个就是缩放,当一个纹路扩大缩小的时分,纹路的像素便不会和屏幕的像素摆放对齐。
    • 另一个原因就是当纹路的起点不在一个像素的鸿沟上。
    在这两种情况下,GPU 需求再做额定的核算。它需求将源纹路上多个像素混合起来,生成一个用来组成的值。当一切的像素都是对齐的时分,GPU 只剩下很少的作业要做。
  • Core Animation东西和模拟器有一个叫做 color misaligned images 的选项,当这些在你的 CALayer 实例中产生的时分,这个功能便可向你展现。

因此, 咱们需求尽量削减视图层次、削减透明视图、layer的扩大缩小.

2. 从CPU作业来看,能够做的优化:

  1. 频繁刷新的杂乱视图,可一次核算好子控件的frame后保存,削减核算量。
  2. 关于加载图片等耗时操作,开启子线程去履行,比方SDWebView用的自己创立的最大并发数为6的并发队列来安排下载任务。
  3. 图片下载完后,设置给UIImageView时,假如咱们不在子线程完结图片的解码,那么解码操作会在主线程完结,容易会形成卡顿。处理办法是在子线程将PNG\JPG\WebP等格式图片画到图形上下文当中后生成CGImage,再回调到主线程将这个CGImage设置给UIImageView;另外, 在处理高分辨率大图时直接解码操作会让内存暴增,这时则需求将图片分段制作到上下文。 详细可参阅 SDWebImage的图片解码源码阅览
  4. 尽量避免离屏烘托。
  5. 你能够运用可变尺度的图画来降低绘图体系的压力。让咱们假设你需求一个 30050 点的按钮插图,这将是 600100=60k 像素或许 60kx4=240kB 内存巨细需求上传到 GPU,而且占用 VRAM。假如咱们运用所谓的可变尺度的图画,咱们只需求一个 5412 点的图画,这将占用低于 2.6k 的像素或许 10kB 的内存,这样就变得更快了。 Core Animation 能够经过 CALayer 的contentsCenter特点来改动图画,大多数情况下,你或许更倾向于运用,-[UIImage resizableImageWithCapInsets:resizingMode:]。 一同留意,在榜首次烘托这个按钮之前,咱们并不需求从文件体系读取一个 60k 像素的 PNG 并解码,解码一个小的 PNG 将会更快。经过这种办法,你的程序在每一步的调用中都将做更少的作业,而且你的视图将会加载的更快。

六、离屏烘托

屏幕外的烘托会烘托图层树中的一部分到一个新的缓冲区中,这需求消耗更多的CPU和GPU时刻,从而导致卡顿。咱们常见的操作代码会导致的有:

1. 暗影: 设置暗影会导致离屏烘托。

self.bgView.layer.shadowColor = [UIColor blackColor].CGColor;//shadowColor暗影颜色
self.bgView.layer.shadowOffset = CGSizeMake(0,0);//shadowOffset暗影偏移,x向右偏移2,y向下偏移6,默许(0, -3),这个跟shadowRadius合作运用
self.bgView.layer.shadowOpacity = 0.3;//暗影透明度,默许0
self.bgView.layer.shadowRadius = 4;//暗影半径,默许3

避免的办法:设置shadowPath

//参数依次为巨细,设置四个角圆角状态,圆角曲度  设置暗影途径可避免离屏烘托
self.bgView.layer.shadowPath = 
[UIBezierPath bezierPathWithRoundedRect:self.bgView.bounds 
byRoundingCorners:UIRectCornerAllCorners 
cornerRadii:CGSizeMake(self.bgView.layer.cornerRadius, self.bgView.layer.cornerRadius)].CGPath;

2. 圆角

设置cornerRadius一同设置masksToBounds=YES会触发离屏烘托,处理办法是设置将图片画到有圆角的rect生成新图片后再进行设置。处理常见的masksToBounds

self.imageView.layer.cornerRadius = 5;
self.imageView.layer.masksToBounds = YES;

3. 遮罩

假如你将mask运用到一个layer上,Core Animation 为了运用这个 mask,会强制进行屏幕外烘托。避免办法:假如不是很必要则不运用。

4. 光栅化

假如你的程序混合了很多图层,而且想要他们一同做动画,GPU 一般会为每一帧(1/60s)重复组成一切的图层。当运用离屏烘托时,GPU 榜首次会混合一切图层到一个根据新的纹路的位图缓存上,然后运用这个纹路来制作到屏幕上。现在,当这些图层一同移动的时分,GPU 便能够复用这个位图缓存,而且只需求做很少的作业。需求留意的是,只有当那些图层不改动时,这才能够用。假如那些图层改动了,GPU 需求重新创立位图缓存。你能够经过设置 shouldRasterize 为 YES 来触发这个行为。

    self.xxview.layer.shouldRasterize = YES;
    self.xxview.layer.contentsScale = [UIScreen mainScreen].scale;

5. 测验离屏烘托的产生

如下图所示,假如是触发了离屏烘托的layer会有黄颜色。

iOS UIView和CALayer

制作像素到屏幕的过程