简单-UIKit性能分析优化

本文是基于UIKit性能调优实战讲解 - 简书这篇文章的总结详细请看原文

实例讲解可以参考:小心别让圆角成了你列表的帧数杀手 - CocoaChina_让移动开发更简单

fps表示frames per second,也就是每秒钟显示多少帧画面。对于静止不变的内容,我们不需要考虑它的刷新率,但在执行动画或滑动时,fps的值直接反映出滑动的流畅程度

调试优化:

1.图层混合:

首先理解像素:像素就是屏幕上的每一个点由RGB三种颜色构成有时候会带有alpha,如果某一个区域覆盖了多个layer,最后的显示效果就会收到影响,显色混合会消耗一定的GPU,当把最上层的颜色透明度设置为100%时GPU就会忽略下面所有的layer节省不必要的运算

第一个调试选项"Color Blended Layers"正是用于检测哪里发生了图层混合,并用红色标记出来。因此我们需要尽可能减少看到的红色区域。一旦发现应该想法设法消除它

解决方案:a.将控件的opaque = true 原理是希望避免图层混合,但是作用不大,因为UIView的这个属性默认就是true只要不是人为的设置为透明都不会出现图层混合,但对于UIImageView他本身和图片都不能为透明的,图片自身的性质也可能会对结果产生影响,比opaque属性更重要的是backgroundColor属性,如果不设置这个属性,控件依然被认为是透明的,所以我们做的是将他设置为白色

PS:如果label文字有中文,依然会出现图层混合,这是因为此时label多了一个sublayer,如果有好的解决办法欢迎告诉我。

2.光栅化光栅化会导致离屏渲染

光栅化是将一个layer预先渲染成位图(bitmap),然后加入缓存中。如果对于阴影效果这样比较消耗资源的静态内容进行缓存,可以得到一定幅度的性能提升。demo中的这一行代码表示将label的layer光栅化:

label.layer.shouldRasterize = true

Instrument中,第二个调试选项是“Color Hits Green and Misses Red”,它表示如果命中缓存则显示为绿色,否则显示为红色,显然绿色越多越好,红色越少越好。

光栅化的缓存机制是一把双刃剑,先写入缓存再读取有可能消耗较多的时间。因此光栅化仅适用于较复杂的、静态的效果。通过Instrument的调试发现,这里使用光栅化经常出现未命中缓存的情况,如果没有特殊需要则可以关闭光栅化

3.颜色格式:

像素在内存中的布局和它在磁盘中的存储方式并不相同。考虑一种简单的情况:每个像素有R、G、B和alpha四个值,每个值占用1字节,因此每个像素占用4字节的内存空间。一张1920*1080的照片(iPhone6 Plus的分辨率)一共有2,073,600个像素,因此占用了超过8Mb的内存。但是一张同样分辨率的PNG格式或JPEG格式的图片一般情况下不会有这么大。这是因为JPEG将像素数据进行了一种非常复杂且可逆的转化。

比如应用中有一些从网络下载的图片,而GPU恰好不支持这个格式,这就需要CPU预先进行格式转化

第三个选项“Color Copied Images”就用来检测这种实时的格式转化,如果有则会将图片标记为蓝色。

4.图片大小

第五个选项“Color Misaligned Images”。它表示如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色

第三个优化是调整所有图片的像素大小以避免不必要的缩放。

5.离屏渲染

正常的渲染通道:OpenGL提交一个命令到Command Buffer,随后GPU开始渲染,渲染结果放到Render Buffer中,这是正常的渲染流程

有一些复杂的效果无法直接渲染出结果,它需要分步渲染最后再组合起来,比如添加一个蒙版(mask):GPU分别得到了纹理(texture,也就是那个相机图标)和layer(蓝色的蒙版)的渲染结果。但这两个渲染结果没有直接放入Render Buffer中,也就表示这是离屏渲染。直到第三个渲染通道,才把两者组合起来放入Render Buffer中。离屏渲染意味着把渲染结果临时保存,等用到时再取出,因此相对于普通渲染更占用资源。

第六个选项“Color Offscreen-Rendered Yellow”会把需要离屏渲染的地方标记为黄色,大部分情况下我们需要尽可能避免黄色的出现。离屏渲染可能会自动触发,也可以手动触发。以下情况可能会导致触发离屏渲染:

重写drawRect方法

有mask或者是阴影(layer.masksToBounds, layer.shadow*),模糊效果也是一种mask

layer.shouldRasterize = true

前两者会自动触发离屏渲染,第三种方法是手动开启离屏渲染。

第四个优化,在设置阴影效果的四行代码下面添加一行:

imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath

这行代码制定了阴影路径,如果没有手动指定,Core Animation会去自动计算,这就会触发离屏渲染。如果人为指定了阴影路径,就可以免去计算,从而避免产生离屏渲染。

设置cornerRadius本身并不会导致离屏渲染,但很多时候它还需要配合layer.masksToBounds = true使用。根据之前的总结,设置masksToBounds会导致离屏渲染。解决方案是尽可能在滑动时避免设置圆角,如果必须设置圆角,可以使用光栅化技术将圆角缓存起来:

// 设置圆角    ps:用后用后无效果

label.layer.masksToBounds = true

label.layer.cornerRadius = 8

label.layer.shouldRasterize = true

label.layer.rasterizationScale = layer.contentsScale

6.快速路径:

之前将离屏渲染和渲染路径时的示意图么,离屏渲染的最后一步是把此前的多个路径组合起来。如果这个组合过程能由CPU完成,就会大量减少GPU的工作。这种技术在绘制地图中可能用到。

第七个选项“Color Compositing Fast-Path Blue”用于标记由硬件绘制的路径,蓝色越多越好。

7.变化区域

刷新视图时,我们应该把需要重绘的区域尽可能缩小。对于未发生变化的内容则不应该重绘,第八个选项“Flash updated Regions”用于标记发生重绘的区域。一个典型的例子是系统的时钟应用,绝大多数时候只有显示秒针的区域需要重绘:

总结

如果你一步一步做到了这里,我想一定会有不少收益。不过,学而不思则罔,思而不学则殆。动手实践后还是应该总结提炼,优化滑动性能主要涉及三个方面:

避免图层混合

确保控件的opaque属性设置为true,确保backgroundColor和父视图颜色一致且不透明

如无特殊需要,不要设置低于1的alpha值

确保UIImage没有alpha通道

避免临时转换

确保图片大小和frame一致,不要在滑动时缩放图片

确保图片颜色格式被GPU支持,避免劳烦CPU转换

慎用离屏渲染

绝大多数时候离屏渲染会影响性能

重写drawRect方法,设置圆角、阴影、模糊效果,光栅化都会导致离屏渲染

设置阴影效果是加上阴影路径

滑动时若需要圆角效果,开启光栅化

实战

本文的demo可以在我的Github上下载,然后一步一步自己体验优化过程。但demo毕竟是刻意搭建的一个环境,我会在我自己的仿写的简书app上不断进行实战优化,欢迎共同学习交流。

代码:

UIView圆角:

#import "UIView+AddCorner.h"

@implementation UIView (AddCorner)

-(double)roundbyuint:(double) num anUint:(double )unit{

double remain = modf(num, &unit);

if (remain > unit / 2.0) {

return [self ceilbyuint:num andUint:unit];

} else {

return [self floorbyuint:num andUint:unit];

}

}

-(double)ceilbyuint:(double)num andUint:(double)unit{

return num - modf(num, &unit) + unit;

//将浮点数num分解成整数部分和小数部分

}

-(double)floorbyuint:(double)num andUint:(double)unit{

return num - modf(num, &unit) ;

}

-(double)piexl:(double)num{

double unit;

switch ((int)[UIScreen mainScreen].scale) {

case 1: unit = 1.0/1.0; break;

case 2: unit = 1.0/2.0; break;

case 3: unit = 1.0/3.0; break;

default: unit = 0.0;

break;

}

return [self roundbyuint:num anUint:unit];

}

-(void)kt_viewAddCorner:(CGFloat)radius andBorderWidth:(CGFloat)borderWidth andBorderColor:(UIColor*)borderColor andBackgroundColor:(UIColor*)backgroundColor{

UIImageView * imageView = [[UIImageView alloc]initWithImage:[self ViewAddCorner:radius andBorderWidth:borderWidth andBorderColor:borderColor andBackgroundColor:backgroundColor]] ;

[self insertSubview:imageView atIndex:0];

}

-(UIImage *)ViewAddCorner:(CGFloat)radius andBorderWidth:(CGFloat)borderWidth andBorderColor:(UIColor*)borderColor andBackgroundColor:(UIColor*)backgroundColor {

CGSize sizeToFit = CGSizeMake((double)CGRectGetWidth(self.frame), (double)CGRectGetHeight(self.frame));

CGFloat halfBorderWidth = (CGFloat)borderWidth / 2.0;

UIGraphicsBeginImageContextWithOptions(sizeToFit, false, [UIScreen mainScreen].scale);

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetLineWidth(context, borderWidth);

CGContextSetStrokeColorWithColor(context, borderColor.CGColor);

CGContextSetFillColorWithColor(context, backgroundColor.CGColor);

CGFloat width = sizeToFit.width, height = sizeToFit.height;

CGContextMoveToPoint(context, width - halfBorderWidth, radius + halfBorderWidth);  // 开始坐标右边开始

CGContextAddArcToPoint(context, width - halfBorderWidth, height - halfBorderWidth, width - radius - halfBorderWidth, height - halfBorderWidth, radius);  // 右下角角度

CGContextAddArcToPoint(context, halfBorderWidth, height - halfBorderWidth, halfBorderWidth, height - radius - halfBorderWidth, radius); // 左下角角度

CGContextAddArcToPoint(context, halfBorderWidth, halfBorderWidth, width - halfBorderWidth, halfBorderWidth, radius); // 左上角

CGContextAddArcToPoint(context, width - halfBorderWidth, halfBorderWidth, width - halfBorderWidth, radius + halfBorderWidth, radius) ;// 右上角

CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);

UIImage  *output = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return output;

}

@end

UIImageView圆角:

#import "UIImageView+AddCorner.h"

@implementation UIImageView (AddCorner)

-(void)kt_ImageViewAddCornerWithRadius:(CGFloat)radius{

// self.image = self.image?:[self.image imageAddCornerWithRadius:radius andSize:self.bounds.size];

//注意第三个选项的设置

UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);

//在绘制之前先裁剪出一个圆形

[[UIBezierPath bezierPathWithRoundedRect:self.bounds

cornerRadius:radius] addClip];

//图片在设置的圆形里面进行绘制

[self.image drawInRect:self.bounds];

//获取图片

self.image = UIGraphicsGetImageFromCurrentImageContext();

//结束绘制

UIGraphicsEndImageContext();

}

@end

UIImage圆角:

#import "UIImage+ImageRoundedCorner.h"

@implementation UIImage (ImageRoundedCorner)

- (UIImage*)imageAddCornerWithRadius:(CGFloat)radius andSize:(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(radius, radius)];

CGContextAddPath(ctx,path.CGPath);

CGContextClip(ctx);

[self drawInRect:rect];

CGContextDrawPath(ctx, kCGPathFillStroke);

UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return newImage;

}

@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 实际开发中,大多会遇到圆角或者圆形的控件的情况。通常,简便的解决方案主要是: 1.让美工做一个圆角的图片...
    LanWor阅读 1,629评论 1 5
  • Quartz2D以及drawRect的重绘机制字数1487 阅读21 评论1 喜欢1一、什么是Quartz2D Q...
    PurpleWind阅读 752评论 0 3
  • 圆角是一种常见UI效果设计,与直角相比要柔和许多,适合暖色调设计。而在设置圆角时会带来一定的性能损耗,下面介绍几种...
    简鱼7819阅读 2,628评论 0 10
  • //设置尺寸为屏幕尺寸的时候self.window = [[UIWindow alloc] initWithFra...
    LuckTime阅读 784评论 0 0
  • 做前端的小伙伴无论在学习还是工作中,应该还是或多或少会遇到点IE兼容问题,当然我们巴不得IE的份额越来越低..( ̄...
    Aleph_Zheng阅读 161评论 0 0