美文网首页
页面优化

页面优化

作者: 三国韩信 | 来源:发表于2020-07-03 18:13 被阅读0次
UIView和UILayer的关系

self.view.backgroupColor = [UIColor redColor];
这句话做了一个隐式的动画提交[CATransaction commit]
那么 Commit Transaction做了什么,大概包括了以下4个步骤:

  • Layout,构建视图
  • Display,绘制视图
  • Prepare,额外的 Core Animation ⼯工作,⽐比如解码
  • Commit,打包图层并将它们发送到 Render Serve
  1. 在第一个步骤中Layout构建视图会循环的去遍历图层,计算frame。循环的调用[layer layoutSublayers] 。而 layoutSublayers会去调用它的delegate对象(UIView)的 layoutSublayersOfLayer方法,在layoutSublayersOfLayer方法中又会去调用layoutSubviews方法。大概的伪代码如下图:


    Layout操作
  2. 第二个步骤中,绘制视图的操作,是由[CALayer dispaly]->[CALayer drawInContext:]->[UIView drawLayer:inContext:]->[UIView drawRect]
    最终来到了我们熟悉的drawRect方法绘制出视图的样子。


    Display流程
  3. 第三和第四个步骤由底层的Core Animation提交个OpenGL ES或者Metal,再由Metal去操作硬件GPU来渲染出画面。

那么UIView和CALayer的关系是啥呢?UIView是它对应layer的delegate,默认的layer是CALayer,当然也可以指定为开发者创建的某个CALayer的子类。


view与layer

view遵守了CALayerDelegate协议,layer的很多方法都是通过它的代理view来实现的。通过自定义一个CALayer的子类来绑定自定义的view,来看其中的关系。

#import <UIKit/UIKit.h>
@interface LGView : UIView
- (CGContextRef)createContext;
- (void)closeContext;
@end

#import "LGView.h"
#import "LGLayer.h"
@implementation LGView

- (void)drawRect:(CGRect)rect {
    // Drawing code, 绘制的操作, BackingStore(额外的存储区域产于的) -- GPU
}

//子视图的布局
- (void)layoutSubviews{
    [super layoutSubviews];
}

+ (Class)layerClass{
    return [LGLayer class];
}

- (void)layoutSublayersOfLayer:(CALayer *)layer{
     [self layoutSubviews];
}

- (CGContextRef)createContext{
    
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    return context;
}

- (void)layerWillDraw:(CALayer *)layer{
    //绘制的准备工作,do nontihing
}

//绘制的操作
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    [[UIColor redColor] set];
       //Core Graphics
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(self.bounds.size.width/2.0-20,self.bounds.size.width/2.0-20, 40, 40)];
    CGContextAddPath(ctx, path.CGPath);
    CGContextFillPath(ctx);
}

//layer.contents = (位图)
- (void)displayLayer:(CALayer *)layer{
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    dispatch_async(dispatch_get_main_queue(), ^{
        layer.contents = (__bridge id)(image.CGImage);
    });
}

- (void)closeContext{
    UIGraphicsEndImageContext();
}
@end
#import <QuartzCore/QuartzCore.h>
@interface LGLayer : CALayer
@end

#import "LGLayer.h"
@implementation LGLayer
   //前面断点调用写下的代码
- (void)layoutSublayers{
    if (self.delegate && [self.delegate respondsToSelector:@selector(layoutSublayersOfLayer:)]) {
        //UIView
        [self.delegate layoutSublayersOfLayer:self];
    }else{
        [super layoutSublayers];
    }
}
//绘制流程的发起函数
- (void)display{
     // Graver 实现思路
    CGContextRef context = (__bridge CGContextRef)([self.delegate performSelector:@selector(createContext)]);
    
    [self.delegate layerWillDraw:self];
    
    [self drawInContext:context];  // 这里会自动回调到其delegate的drawLayer:inContext方法里。即自动调用LGView的对应方法
    
    [self.delegate displayLayer:self];

    [self.delegate performSelector:@selector(closeContext)];
}
@end

在LGView中的+ (Class)layerClass可以看出,是吧LGView的layer绑定到了LGLayer上。那么LGView就成了LGLayer的delegate。在LGLayer的display方法和layoutSublayers方法中看到,有出现了很多self.delegate去调用的方法,它就会自动的回调到LGView里的对应方法里去。还有layer 的drawInContext:方法也会自动的调用其delegate的drawLayer:inContext 方法。

通过这样把layer和view关联了起来,那么可能会有人问,为什么这么麻烦,直接把layer渲染显示的功能加到UIView上,或者把UIView的功能都加到layer,合成一个不就好么?

这是一种设计思想——职责单一化。layer的作用渲染图片、动画等。UIView的功能有处理点击事件、管理子View等。各自的功能分开,而且layer单独抽出,他不止可以为iOS服务,还可以Mac,还可以iWatch等等。封装和设计的思想,细细品!

页面卡顿检测
  1. 通过RunLoop来检测
  2. 通过FPS来检测 比如YYKit 框架来检测。
  3. 微信的卡顿检测方案——matrix框架
页面卡顿优化
  1. 预排版,在子线程里提前计算好页面的布局。比如tableView中cell高度提前计算好,不要再heightForRowAtIndexPath的回调的里去计算。
  2. 页面中有出现圆角的情况,不要直接用view.layer.cornerRaudius去设置圆角。(会触发离屏渲染)能让UI切圆角图的,最好找UI去切图,不能的自己去画圆角。
  3. 预解码。把图片的解码的操作从主线程放到子线程去做。我们平时常用的SDImage或者YYImage框架其实已经做了这样的操作了。
// 调用SDImage的这个方法,把解码成位图的操作放到子线程,然后通过block回调回来。然后在block里去自己画圆角的操作。
 [self.iconButton sd_setImageWithURL:[NSURL URLWithString:layout.timeLineModel.iconUrl] forState:UIControlStateNormal placeholderImage:nil options:SDWebImageAvoidAutoSetImage completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
        [self.iconButton setImage:[image cornerRadius:self.iconButton.bounds.size.width/ 2.0 size:self.iconButton.bounds.size] forState:UIControlStateNormal]; 
    }];
// 自定画圆角
#import "UIImage+cornerRadious.h"
@implementation UIImage (cornerRadious)

- (UIImage *)cornerRadius:(CGFloat)cornerRadius size:(CGSize)size{
    CGRect rect = CGRectMake(0, 0, size.width, size.height);
    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
    CGContextAddPath(ctx,path.CGPath);
    CGContextClip(ctx);
    [self drawInRect:rect];
    CGContextDrawPath(ctx, kCGPathFillStroke);
    UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
@end
  1. 减少图层。 尽可能的减少图层的嵌套。图层越多,CPU的frame的计算、GPU的渲染也会耗时越多。如果不能避免很多图层的嵌套,可以考虑把整个图层合成一张位图(这个操作也很麻烦。。。)
  2. 异步渲染 这里推荐美团开源的Grave框架,不过它因为某些原因下架了。
离屏渲染

在上面的优化中,有说到设置圆角会触发离屏渲染,那么什么是离屏渲染?为什么圆角会触发离屏渲染?还有哪些会触发离屏渲染?出现了离屏渲的影响等也都是在面试的过程中会被问到的?

  • 什么是离屏渲染
    在说什么是离屏渲染之前,先来了解一下,我们代码上的view是如何被渲染到屏幕上的。
    简单的说,App上的一个view,经过一系列的操作后,会被放到帧缓冲区Frame Buffer里,然后等待被渲染的屏幕上。可是遇到一些稍微复杂的图像,当CPU和GPU不能够一次性的把图片渲染完放到Frame Buffer,那么此时就需要一个额外的存储空间来存放临时的图像信息,这个额外的存储空间就叫Offscreen Buffer(离屏缓冲区)。
正常渲染简易流程 触发了离屏渲染的流程.png
  • 为什么设置圆角的时候会触发离屏渲染
    在知道了什么是离屏渲染之后,那么在平常开发的过程中总是听说设置了圆角会触发离屏渲染。这其中的原理是啥?是不是所有的设置圆角都会有离屏渲染的情况?(上代码)
    UIImageView *img1 = [[UIImageView alloc]init];
    img1.frame = CGRectMake(100, 420, 100, 100);
    img1.backgroundColor = [UIColor blueColor];
    img1.layer.cornerRadius = 50;
    img1.layer.masksToBounds = YES;
    img1.image = [UIImage imageNamed:@"btn.png"];
    [self.view addSubview:img1];

众所周知,上面的代码会触发离屏渲染,因为设置了img1.layer.cornerRadius = 50; img1.layer.masksToBounds = YES; 这是为何? 苹果baba对cornerRadius 属性的解释是——当设置了cornerRadius的时候,只会设置layer 的background color 和border为圆角,而它的content不会默认被设置为圆角,只有同时设置了masksToBounds属性时,content才会被设置成圆角。


cornerRadius的属性解释
设置圆角的情况

根据上面离屏渲染的定义可知UIImageView在被渲染到屏幕的过程中,它需要同时对imageView的自身的background color去设置圆角和其image图片去设置圆角,这2个操作它不是一次性把做完后放到frame buffer里去的。它需要offScreen buffer来存中间的temp。在offScreen buffer这2者都操作完成了再输入到frame buffer,等待渲染到屏幕上。

设置圆角渲染的流程图

那么是不是所有的设置圆角都会触发离屏渲染呢?通过上面的渲染流程可以看出,因为imageView本身background color和content都要设置圆角才需要offscreen buffer这个临时空间来存储。那么是不是当imageview的background color属性不赋值的时候,就不会离屏渲染呢?答案是肯定的,当只有imageView的content需要设置圆角的时候,是不需要offscreen存在的,一步到位的渲染好了放到帧缓冲区里等待上屏。showCode:

  // 当backgroundColor属性不赋值的时候不会触发离屏渲染
    UIImageView *img1 = [[UIImageView alloc]init];
    img1.frame = CGRectMake(100, 420, 100, 100);
    //img1.backgroundColor = [UIColor blueColor];
    img1.layer.cornerRadius = 50;
    img1.layer.masksToBounds = YES;
    img1.image = [UIImage imageNamed:@"btn.png"];
    [self.view addSubview:img1];
  • 还有哪些行为会触发离屏渲染
  1. 使用了 mask 的 layer (layer.mask)
  2. 需要进行裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
  3. 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/ layer.opacity)
  4. 添加了投影的 layer (layer.shadow*)
  5. 毛玻璃效果的图片
  6. 采用了光栅化的 layer (layer.shouldRasterize)
    7.绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
  • 离屏渲染的影响
    开发过程中最常见的就是在tableView或者collectionView中cell里有圆角的imageView或view。tableView在滚动的时候,每一帧变化都会触发每个cell的重新绘制,因此一旦存在离屏渲染,本来直接输出到frame buffer的操作就会被打断,要临时开辟一块空间(offscreen buffer)来进行“圆角”操作,然后在回到frame buffer。这一个切换在每秒会发生60次,并且很可能每一帧有几十张的图片要求这么做,对于GPU的性能冲击可想而知。故在tableView或者collectionView这种控件中尽可能的减少离屏渲染的情况出现。
  • 圆角代码解析
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    //1.按钮存在背景图片(2个图层)
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 30, 100, 100);
    btn1.layer.cornerRadius = 50;
    [self.view addSubview:btn1];
    [btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
    btn1.layer.masksToBounds = YES;
    
    //2.按钮不存在背景图片
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn2.frame = CGRectMake(100, 150, 100, 100);
    btn2.layer.cornerRadius = 50;
    btn2.backgroundColor = [UIColor blueColor];
    [self.view addSubview:btn2];
    btn2.clipsToBounds = YES;
    
    UIButton *btn3 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn3.frame = CGRectMake(100, 280, 100, 100);
    [self.view addSubview:btn3];
    [btn3 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
    
    //3.UIImageView 设置了图片+背景色;
    UIImageView *img1 = [[UIImageView alloc]init];
    img1.frame = CGRectMake(100, 420, 100, 100);
    img1.backgroundColor = [UIColor blueColor];
    [self.view addSubview:img1];
    img1.layer.cornerRadius = 50;
    img1.layer.masksToBounds = YES;
    img1.image = [UIImage imageNamed:@"btn.png"];
    
    //4.UIImageView 只设置了图片,无背景色;
    UIImageView *img2 = [[UIImageView alloc]init];
    img2.frame = CGRectMake(100, 560, 100, 100);
    //img2.backgroundColor = [UIColor blueColor];
    [self.view addSubview:img2];
    img2.layer.cornerRadius = 50;
    img2.layer.masksToBounds = YES;
    img2.image = [UIImage imageNamed:@"btn.png"];
    
}

上面的代码中,btn1、btn2、btn3、img1、img2五中情况是否都会触发离屏渲染呢?答案如图,只有btn1、和img1会,其他的不会。

image.png

解析1:在第一张图中,存在这2个图层,btn1和imageview,这2个layer都设置了圆角,那么肯定需要offscreen buffer来中间缓存着。

解析2:在第二张图中,只是btn2自己本身设置了cornerRadius,没有其他图层的需要渲染,可以直接放到到frame buffer等到上屏,不需要offscreen buffer,不会触发离屏渲染。

解析3:在第三张图中,虽然有btn3和imageView的存在,但他两只是单纯的图层的叠加,没有其他特殊的渲染操作,CUP会先把btn从帧缓冲区拿出来渲染到屏幕,再把imageview从帧缓冲区拿出来渲染到屏幕,这个过程不需要用到offscreen buffer,也不会触发离屏渲染。

解析4:在第四张图的情况就是我们常常见到的会触发离屏渲染的情况,具体的流程如上面的设置圆角渲染流程图所示。

解析5:第5张图和第4张图就差了一句设置backgroundColor的代码。结果第5中就不会出现离屏渲染。在img2中,没有设置backgroundColor,只有其位图需要设置圆角,这种情况不需要offscreen buffer,直接放到frame buffer等到上屏即可。

相关文章

  • 前端性能初步优化

    性能优化有两个方向 优化页面渲染 减小页面体积,提升网络加载 优化页面渲染 对于优化页面渲染可以进行如下方案来进行...

  • 「页面架构」页面优化

    页面优化 页面优化可以提升页面的访问速度从而提高用户体验,优化的页面可以更好的提升 SEO 的效果同时也可以提高代...

  • iOS性能优化之页面加载速率

    iOS性能优化之页面加载速率 iOS性能优化之页面加载速率

  • 沈洛:单页面优化教程

    单页面搜索引擎优化的工作是个精细的工作,单页面优化需要把优化工作做到极致。所以,单页面优化工作不光考察你的SEO技...

  • 单页面关键词布局优化

    网站文章页优化,既可以是只有一个页面的站点,也可以是拥有众多页面的网站的某一个页面。 一、单页面元素优化 元素优化...

  • 网站文章页优化知识

    网站文章页优化,既可以是只有一个页面的站点,也可以是拥有众多页面的网站的某一个页面。 一、单页面元素优化 元素优化...

  • 分析网站优化布局思路与SEO细节

    大多数做SEO的人不会忽视网站的页面优化。很多人把注意力集中在网站主页的页面优化上。网站页面优化也是网站内部优化的...

  • pc端的性能优化

    pc端性能优化 一、页面优化的常用工具 网站慢的因素很多 二、网站优化 1、页面优化 2、服务器端优化 3、主观优...

  • 单页面优化的优势

    1、单页面优化是什么 顾名思义,单页面就是一个页面的SEO优化,需要将一个页面的定位关键词优化上去。它和一般的整站...

  • 网站单页面优化的技巧

    网站单页面优化的技巧 单页面就是只有一个页面,没有多个页面的跳转点击,刚开始做seo的新手可能会觉得,单页面优化起...

网友评论

      本文标题:页面优化

      本文链接:https://www.haomeiwen.com/subject/pvsvfktx.html