之前的文章里,我们探讨了基于多种不同技术来实现自定义的UIButton,当然不同的技术所涉及到的代码复杂度和难度也不一样。但是我也有意提到了基于不同方法的实现所体现出的性能表现也不一一相同。
 
【在屏幕背后的东西】
为了了解性能是如何受到影响的,我们需要进一步地观察iOS里图形实现背后的一些内容。下面这张图呈现了不同的frameworks和libraries之间的一些联系:
在最顶层的就是UIKit,一个在iOS中用来管理用户图形交互的Objc高级的框架,它由一系列的集合类构成,例如UIButton、UILabel,每一个都负责他们指定的UI Control角色。UIKit本身构建在一个叫Core Animation的框架之上,它因为被用于处理更为强大的平滑的转场效果而引入OS X Leopard和iOS而出名。
 
再往下深入一点就是OpenGL ES了,一个用在移动设备上绘制2D和3D计算机图形的标准开源库,它广泛地被用在游戏的图形绘制上,同样在Core Animation和UIKit中发挥着强大的作用。
 
最后一部分就是Core Graphics,曾经在Quartz(一个基于CPU的绘制引擎,在OS X系统上初次露脸)中被引入。这两个较为底层的框架都是用C语言编写的。
 
上图中的最底下一行是硬件层,由GPU和CPU组成。
 
我们经常说到的硬件加速其实是指OpenGL,Core Animation/UIKit基于GPU之上对计算机图形合成以及绘制的实现,直到目前为止,iOS上的硬件加速能力还是大大领先与android,后者由于依赖CPU的绘制,绝大多数的动画实现都会让人感觉明显的卡顿。
 
而离屏绘制(Offscreen drawing)的话就是指GPU一边在当前屏幕上进行绘制,而另一边在屏幕还没有处理图像信息之前通过CPU来生成图像信息的处理过程。在iOS当中,离屏绘制在以下的情况下会自动触发:
* Core Graphics(任何以CG开头的类)
* 在drawRect方法里,甚至是空方法实现
* 所有shouldRasterize属性是YES的CALayers对象    
* 所有用了masks(setMasksToBounds)和动态阴影的(setShadow*)的CALayers对象
* 所有文字的绘制,包括CoreText
* Group opacity(UIViewGroupOpacity)
 
总地来说,当有涉及到动画的时候离屏绘制就会影响到性能,你可以通过Instruments进行真机调试,从而检测到底是哪部分UI正在进行离屏绘制:
1.接入设备
 
2.在XCode的Developer Applications里打开Instruments(Command+Shift+i)   
3.选择iOS>Graphics>Core Animation template 
4.打开详情面板,选择适当的窗口模式 
5.选择你的target设备
 
6.检查Color Offscreen-Rendered Yellow的debug选项
 
7.在你设备上所有的离屏绘制都会呈现出黄色的色调
现在让我们逐一检查上一篇文章里涉及的一些技术点的性能表现。
 
【预资源加载绘制】
使用UIImage来加载原先就在磁盘中的图片作为自定义UIButton的背景图片的话,完全依赖与GPU的绘制。而且如果用可resizable的图片,也可适当避免当背景大小动态变换的时候需要不同大小的图片,这样可以让我们App的bundle做得更小,而且在绘制像素的图的时候也重复地利用了硬件加速。
 
【使用CALayer】
因为基于CALayer实现的方法里我们使用到了遮罩去绘制圆角的效果,所以这里会涉及到离屏绘制。当我们使用Core Animation框架的时候,如果上面提到的用遮罩绘制圆角的属性是开启的话,我们也必须明确地禁止使用动画效果。作为底线,除非你非要进行一个动画转场效果,否则这种方法并不适用。
 
【使用drawRect】
drawRect方法依赖Core Graphics框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch事件的方式:每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例。
 
【技巧混合 hybrid approach】
这样来说的话,是不是就意味着使用预资源加载进行绘制就是唯一可行的方案了呢?答案是否定的。如果你坚持需要用代码来灵活地进行绘制的话,还是有一些优化代码还有减少性能消耗方面的技巧的。有一种可行的方案就是创建stretchable的位图并在多个实例对象之间进行重用。
 
我们按照在之前的教程的相同步骤创建一个新的UIButton的子类,然后如下定义一些静态变量
 // In CBHybrid.m
 #import "CBHybrid.h"
 @implementation CBHybrid
    
// Resizable background image for normal state static UIImage *gBackgroundImage;
    
// Resizable background image for highlighted state static UIImage *gBackgroundImageHighlighted;
    
// Background image border radius and height
 static int borderRadius = 5;
 static int height = 37;
接下来我们把CBBezie内drawRect里的代码换个地方,通过一系列的改变之后:我们可以创建一个resizable的Image用来取代之前固定尺寸的Image,然后我们可以持有该静态对象并便于重用
    - (UIImage *)drawBackgroundImageHighlighted:(BOOL)highlighted {
        // Drawing code goes here
    }
首先,我们需要知道我们resizable Image的宽,为了优化性能,我们应该在图片的中间保留一个像素的宽
float width = 1 + (borderRadius * 2);
高的话在这个case里就不是非常重要了,因为这个按钮的高度对渐变层来说已经足够可见,设置为37pt的话也是出于和其余几个按钮的高度相同的原因。
搞定之后,我们需要一个bitmap context来进行绘制,搞起~
UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UIGraphicsBeginImageContextWithOptions第二个参数为NO的话确保我们创建的Image context是透明的(带Alpha),最后的参数是scale factor(屏幕密度),如果是0的话就是当前设备的默认scale factor。
接下来的代码就和我们之前用Core Graphics来实现CBBezier的Demo里用到的非常相像了。我们用highlighted参数替换默认的self.highlighted属性,把用作更新界面的图像的信息值保存下来。
// Gradient Declarations
// NSArray *gradientColors = ...
    
// Draw rounded rectangle bezier path
UIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, width, height) cornerRadius: borderRadius];
    
// Use the bezier as a clipping path
[roundedRectanglePath addClip];
    
// Use one of the two gradients depending on the state of the button
CGGradientRef background = highlighted? highlightedGradient : gradient;
    
// Draw gradient within the path
CGContextDrawLinearGradient(context, background, CGPointMake(140, 0), CGPointMake(140, height-1), 0);
    
// Draw border
// [borderColor setStroke...
    
// Draw Inner Glow
// UIBezierPath *innerGlowRect...

我们唯一需要添加的步骤就是,用UIGraphicsEndImageContext来保存图像信息,并且放到UIImage对象中。

UIImage* backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
    
// Cleanup
UIGraphicsEndImageContext();
现在我们已经完成了创建背景图片的方法,接下来我们必须实现一个通用的初始化方法用来实例化Images,并且把他们设置为CBHybird实例真正的背景。
- (void)setupBackgrounds {
 
// Generate background images if necessary
    if (!gBackgroundImage &&!gBackgroundImageHighlighted) {
        gBackgroundImage = [[self drawBackgroundImageHighlighted:NO] resizableImageWithCapInsets:UIEdgeInsetsMake(borderRadius, borderRadius, borderRadius, borderRadius) resizingMode:UIImageResizingModeStretch];
        gBackgroundImageHighlighted = [[self drawBackgroundImageHighlighted:YES] resizableImageWithCapInsets:UIEdgeInsetsMake(borderRadius, borderRadius, borderRadius, borderRadius) resizingMode:UIImageResizingModeStretch];
    }
 
// Set background for the button instance
    [self setBackgroundImage:gBackgroundImage forState:UIControlStateNormal];
    [self setBackgroundImage:gBackgroundImageHighlighted                     forState:UIControlStateHighlighted];
}
我们可以用custom的类型来实例化我们的CBHybird,当然也可以用initWithCoder,如果想用在代码里实现的话我们还可以用initWithFrame(···其实这里我是不想翻译的,原作者写得真是太详细了,第一次翻译技术文章,还是彻底点吧。。。)
    
+ (CBHybrid *)buttonWithType:(UIButtonType)type
{
   return [super buttonWithType:UIButtonTypeCustom];
}
    
- (id)initWithCoder:(NSCoder *)aDecoder 
{
    self = [super initWithCoder:aDecoder];
    if (self) 
       [self setupBackgrounds];
    return self;
}
为了确保我们新建的BHybird类能正常使用,在Interface Builder里我们赋值一个button,把实现类改成CBHybird后,把button的content内容改为CGContext-generated image(便于区分)。是驴是马,咱们cmd+R跑起来试试。
 
完整的子类实现代码在这里~~~
 
【结束语】
所有都搞定之后,我们发现用预资源加载绘制仍然比任何一种基于代码实现的方案表现得更为出色。然而,Core Graphicsis在效率和灵活性上更有优势。因此,基于两种技巧的混合方案在现在看来包含了以上两个的长处,而且在性能上也并没有明显的影响。
---Over---

iOS图形处理和性能(转)的更多相关文章

  1. iOS图形处理和性能

    转自陶丰平的博客   原文的题目是Designing for iOS: Graphics & Performance,晚上花了两个不到小时大致翻译了下. ---Begin--- 在之前的文章里 ...

  2. 25个增强iOS应用程序性能的提示和技巧(高级篇)(2)

    25个增强iOS应用程序性能的提示和技巧(高级篇)(2) 2013-04-16 14:56 破船之家 beyondvincent 字号:T | T 在开发iOS应用程序时,让程序具有良好的性能是非常关 ...

  3. 25个增强iOS应用程序性能的提示和技巧(高级篇)(1)

    25个增强iOS应用程序性能的提示和技巧(高级篇)(1) 2013-04-16 14:56 破船之家 beyondvincent 字号:T | T 在开发iOS应用程序时,让程序具有良好的性能是非常关 ...

  4. 25个增强iOS应用程序性能的提示和技巧(中级篇)(3)

    25个增强iOS应用程序性能的提示和技巧(中级篇)(3) 2013-04-16 14:42 破船之家 beyondvincent 字号:T | T 本文收集了25个关于可以提升程序性能的提示和技巧,分 ...

  5. 25个增强iOS应用程序性能的提示和技巧(中级篇)(2)

    25个增强iOS应用程序性能的提示和技巧(中级篇)(2) 2013-04-16 14:42 破船之家 beyondvincent 字号:T | T 本文收集了25个关于可以提升程序性能的提示和技巧,分 ...

  6. 25个增强iOS应用程序性能的提示和技巧--中级篇

    25个增强iOS应用程序性能的提示和技巧--中级篇 标签: ios性能优化内存管理 2013-12-13 10:55 738人阅读 评论(0) 收藏 举报  分类: IPhone开发高级系列(34)  ...

  7. 25个增强iOS应用程序性能的提示和技巧(初级篇)

    25个增强iOS应用程序性能的提示和技巧(初级篇) 标签: ios内存管理性能优化 2013-12-13 10:53 916人阅读 评论(0) 收藏 举报  分类: IPhone开发高级系列(34)  ...

  8. 25 个增强iOS应用程序性能的提示和技巧 应用程序性能的提示和技巧

    初级 在开发过程中,下面这些初级技巧需要时刻注意: 1.使用ARC进行内存管理2.在适当的情况下使用reuseIdentifier3.尽可能将View设置为不透明(Opaque)4.避免臃肿的XIBs ...

  9. 增强iOS应用程序性能的提示和技巧(25个)

    转自 http://www.cocoachina.com/newbie/basic/2013/0522/6259.html 在开发iOS应用程序时,让程序具有良好的性能是非常关键的.这也是用户所期望的 ...

随机推荐

  1. 【HDU 2577】How to Type

    题意 (我做了这题才知道caps lock 锁定大小写后,按一下shift键可以输入相反的大小写.) 这题就是给你只有大小写字母的字符串,求最少多少次按键盘.最后caps lock 必须是关闭的. 分 ...

  2. CSS文字排版

    一.font-size 我来试一试:为第一段中的“胆小如鼠”设置字号为:20px,字体颜色为:red. <!DOCTYPE HTML> <html> <head> ...

  3. The constructor ClassPathXmlApplicationContext(String) refers to the missing type BeansException

    Spring4.X配置一个简单的读取sping的xml的配置文件结果

  4. POJ1141 Brackets Sequence

    Description Let us define a regular brackets sequence in the following way: 1. Empty sequence is a r ...

  5. 中间件、MetaQ入门学习

    目录 . 中间件技术 . MetaQ中间件 . MetaQ编程实践 1. 中间件技术 0x1: 中间件简介 中间件(Middleware)是提供系统软件和应用软件之间连接的软件,以便于软件各部件之间的 ...

  6. Eclipse开发Android程序如何在手机上运行

    android开发不论是在真机上调试还是最终发布到真机上都非常简单,过程如下: 1.安装usb驱动 手机要能与电脑相连,当然要安驱动了.效果就是你插入手机,电脑显示驱动已识别.驱动安装的官方教程:ht ...

  7. Java Web文件上传

    参考资料:http://www.cnblogs.com/xdp-gacl/p/4200090.html 一.问题描述 Java Web文件上传需要借助一些第三方库,常用的是借助Apache的包,有两个 ...

  8. 什么是RST包,什么是三次握手,什么是四次握手 ---请进

    一.RST包.本人学习后总结:RST包用于强制关闭TCP链接. TCP连接关闭的正常方法是四次握手.但四次握手不是关闭TCP连接的唯一方法. 有时,如果主机需要尽快关闭连接(或连接超时,端口或主机不可 ...

  9. Linux建立软连接

    实例:ln -s /home/gamestat    /gamestat linux下的软链接类似于windows下的快捷方式 ln -s a b 中的 a 就是源文件,b是链接文件名,其作用是当进入 ...

  10. 前端框架react研究

    摘要: 最近公司要做一个嵌套在app中的应用,考虑着用Facebook的react来开发view,所以就研究了下.下面是我在开发中遇到的坑,希望能给你帮助. 项目地址:https://github.c ...