drawRect或不绘制drawRect(何时应该使用drawRect/Core Graphics vs subviews/images以及为什么?)

Bob*_*ryn 139 iphone core-graphics ios

为了澄清这个问题的目的:我知道如何使用子视图和使用drawRect创建复杂的视图.我试图完全理解何时使用一个而不是另一个.

我也明白,提前优化这一点是没有意义的,并且在进行任何分析之前做一些更难的方法.考虑到我对这两种方法感到满意,现在真的想要更深刻的理解.

我的很多困惑来自学习如何使表格视图滚动性能真正流畅和快速.当然,这种方法的原始来源是来自twitter for iPhone(以前的tweetie)的作者.基本上它说,为了使表格滚动黄油光滑,秘诀是不使用子视图,而是在一个自定义uiview中完成所有绘图.从本质上讲,使用大量子视图似乎会减慢渲染速度,因为它们有很多开销,并且经常在父视图上重新合成.

公平地说,这是在3GS是一个非常新的品牌时写的,iDevices从那以后变得更快.不过这种方法经常 建议interwebs和其他高性能表.事实上,它是Apple的表示例代码中的一种建议方法,已经在几个WWDC视频(iOS开发人员的实用绘图)和许多iOS 编程书籍中提出过.

甚至还有很棒的工具来设计图形并为它们生成Core Graphics代码.

所以起初我会相信"核心图形存在的原因.它很快!"

但是当我认为我得到"尽可能支持核心图形"的想法时,我开始看到drawRect经常导致应用程序响应能力差,内存非常昂贵,并且真正对CPU征税.基本上,我应该" 避免覆盖drawRect "(WWDC 2012 iOS App性能:图形和动画)

所以我想,就像一切一样,它很复杂.也许你可以帮助自己和其他人了解使用drawRect的时间和原因?

我看到一些使用Core Graphics的明显情况:

  1. 你有动态数据(Apple的股票图表示例)
  2. 您有一个灵活的UI元素,无法使用简单的可调整大小的图像执行
  3. 您正在创建动态图形,一旦渲染在多个位置使用

我看到避免Core Graphics的情况:

  1. 视图的属性需要单独设置动画
  2. 你有一个相对较小的视图层次结构,因此任何使用CG的额外努力都不值得获得
  3. 您想要更新视图的各个部分而不重绘整个视图
  4. 父视图大小更改时,子视图的布局需要更新

所以赐予你的知识.您在什么情况下可以使用drawRect/Core Graphics(也可以通过子视图完成)?哪些因素会导致您做出这个决定?如何/为什么在一个自定义视图中绘制建议用于黄油平滑表格单元格滚动,但Apple建议drawRect反对性能原因一般?那么简单的背景图像(你何时使用CG创建它们与使用可调整大小的png图像)?

制作有价值的应用程序可能不需要深入理解这个主题,但我不喜欢在不能解释原因的情况下在技术之间进行选择.我的大脑生我的气.

问题更新

感谢大家的信息.这里有一些澄清问题:

  1. 如果你正在绘制具有核心图形的东西,但是可以用UIImageViews和预渲染的png完成同样的事情,你应该总是走这条路吗?
  2. 一个类似的问题:特别是像这样的badass工具,你何时应该考虑在核心图形中绘制界面元素?(可能是元素的显示是可变的.例如,具有20种不同颜色变化的按钮.还有其他情况吗?)
  3. 根据我在下面的回答中的理解,通过在复杂的UIView渲染自身后有效捕获单元格的快照位图,并在滚动和隐藏复杂视图时显示,可以获得表格单元格相同的性能提升吗?显然有些部分必须要制定出来.我有一个有趣的想法.

小智 71

尽可能坚持使用UIKit和子视图.您可以提高工作效率,并利用所有易于维护的OO机制.当你无法从UIKit中获得所需的性能时使用Core Graphics,或者你知道在UIKit中尝试破解绘图效果会更复杂.

一般工作流程应该是使用子视图构建tableviews.使用Instruments测量应用程序支持的最旧硬件的帧速率.如果你不能获得60fps,请下载到CoreGraphics.当你做了一段时间之后,你就会明白UIKit可能是浪费时间的.

那么,为什么Core Graphics快速?

CoreGraphics并不是很快.如果它一直被使用,你可能会变慢.它是一个丰富的绘图API,需要在CPU上完成工作,而不是许多卸载到GPU的UIKit工作.如果你不得不在屏幕上移动一个球,那么每秒在视图上调用setNeedsDisplay是一个糟糕的主意.因此,如果您的视图的子组件需要单独设置动画,则每个组件应该是一个单独的层.

另一个问题是,当你不使用drawRect进行自定义绘图时,UIKit可以优化股票视图,因此drawRect是一个无操作,或者它可以采用合成的快捷方式.当你覆盖drawRect时,UIKit必须采用慢速路径,因为它不知道你在做什么.

在表格视图单元格的情况下,这两个问题可能会被优势所抵消.在视图首次出现在屏幕上时调用drawRect,内容被缓存,滚动是由GPU执行的简单转换.因为您正在处理单个视图而不是复杂的层次结构,所以UIKit的drawRect优化变得不那么重要了.因此,瓶颈成为您可以优化Core Graphics绘图的程度.

只要你可以,使用UIKit.做最简单的实现.轮廓.当有激励时,优化.


uli*_*ess 52

不同之处在于UIView和CALayer基本上处理固定图像.这些图像被上传到图形卡(如果您了解OpenGL,则将图像视为纹理,将UIView/CALayer视为显示此类纹理的多边形).一旦图像在GPU上,即使在其他图像之上具有不同级别的alpha透明度,也可以非常快速地绘制,甚至几次,并且(略微降低性能).

CoreGraphics/Quartz是一个用于生成图像的API .它需要一个像素缓冲区(再次考虑OpenGL纹理)并更改其中的单个像素.这一切都发生在RAM和CPU上,只有Quartz完成后,图像才会"刷新"回GPU.这种从GPU获取图像,更改图像,然后将整个图像(或至少相当大的一部分)上传回GPU的往返行程相当慢.此外,Quartz所做的实际绘图虽然对你正在做的事情非常快,但却比GPU的速度慢.

这是显而易见的,考虑到GPU主要在大块中移动未更改的像素.石英确实像素和股网CPU的随机存取,音频等.另外,如果你有你画的使用Quartz在同一时间几个要素,你必须重新绘制所有的人,当一个人改变,然后上传整个块,如果您更改一个图像,然后让UIViews或CALayers将其粘贴到您的其他图像上,您就可以将更少量的数据上传到GPU.

当你没有实现-drawRect:时,大多数视图都可以被优化掉.它们不包含任何像素,因此无法绘制任何内容.其他视图,如UIImageView,只绘制一个UIImage(同样,它本质上是对纹理的引用,可能已经加载到GPU上).所以,如果你使用一个UIImageView得出同样的UIImage 5倍,它只是上传到GPU一次,然后吸引到5个不同的位置显示,节省了我们的时间和CPU.

实现-drawRect:时,会导致创建新图像.然后使用Quartz在CPU上绘制它.如果您在drawRect中绘制UIImage,它可能会从GPU下载图像,将其复制到您要绘制的图像中,一旦完成,将图像的第二个副本上传回图形卡.所以你在设备上使用了两倍的GPU内存.

因此,最快的绘制方式通常是将静态内容与更改内容分开(在单独的UIViews/UIView子类/ CALayers中).将静态内容加载为UIImage并使用UIImageView绘制它,并将在运行时动态生成的内容放在drawRect中.如果您有重复绘制的内容,但本身不会更改(即3个图标显示在同一个插槽中以指示某些状态)也使用UIImageView.

一个警告:有太多UIViews这样的事情.特别透明的区域在GPU上需要更大的收费来绘制,因为它们需要在显示时与其后面的其他像素混合.这就是为什么你可以将UIView标记为"不透明"的原因,以向GPU表明它可以消除该图像背后的所有内容.

如果您有在运行时动态生成但在应用程序生命周期内保持不变的内容(例如包含用户名的标签),那么使用Quartz绘制整个内容实际上是有意义的,使用文本,按钮边框等,作为背景的一部分.但这通常是一种不需要的优化,除非Instruments应用程序以不同的方式告诉您.


Bob*_*ryn 26

我将尝试在这里总结一下我从其他答案中推断出来的内容,并在原始问题的更新中要求澄清问题.但我鼓励其他人保持答案,并对那些提供了良好信息的人进行投票.

一般的做法

很明显,Ben Sandofsky在他的回答中提到的一般方法应该是"只要你可以,就使用UIKit.做最简单的实现是有效的.简介.当有激励时,优化."

为什么

  1. iDevice中有两个主要的可能瓶颈,即CPU和GPU
  2. CPU负责视图的初始绘制/渲染
  3. GPU负责大部分动画(核心动画),图层效果,合成等.
  4. UIView内置了许多优化,缓存等,用于处理复杂的视图层次结构
  5. 当重写drawRect时,你会错过UIView提供的许多好处,而且它通常比让UIView处理渲染要慢.

在一个平面UIView中绘制单元格内容可以极大地提高滚动表格上的FPS.

就像我上面说的那样,CPU和GPU是两个可能的瓶颈.因为它们通常处理不同的事情,所以你必须注意你遇到的哪个瓶颈.在滚动表的情况下,并不是Core Graphics绘图速度更快,这就是为什么它可以大大提高你的FPS.

实际上,Core Graphics可能比初始渲染的嵌套UIView层次结构慢.但是,看起来不稳定滚动的典型原因是你是GPU的瓶颈,所以你需要解决这个问题.

为什么覆盖drawRect(使用核心图形)可以帮助表滚动:

根据我的理解,GPU不负责视图的初始渲染,而是渲染纹理或位图,有时具有一些图层属性.然后它负责合成位图,渲染所有这些层影响,以及大多数动画(Core Animation).

在表格视图单元格的情况下,GPU可能会受到复杂视图层次结构的瓶颈,因为它不是动画化一个位图,而是为父视图设置动画,进行子视图布局计算,渲染图层效果以及合成所有子视图.因此,对于相同的像素区域,它不是动画一个位图,而是负责一组位图的关系,以及它们如何相互作用.

总而言之,在一个视图中使用核心图形绘制单元格的原因可以加快表格滚动速度,这不是因为它的绘制速度更快,而是因为它减少了GPU上的负载,这是在特定情况下给您带来麻烦的瓶颈.


Wol*_*ink 6

我是游戏开发人员,当我的朋友告诉我基于UIImageView的视图层次结构会减慢我的游戏速度并使其变得糟糕时,我也会问同样的问题.然后我开始研究我能找到的关于是否使用UIViews,CoreGraphics,OpenGL或第三方如Cocos2D的所有内容.我从WWDC的朋友,老师和Apple工程师那里得到的一致答案是,最终不会有太大差异,因为在某种程度上他们都在做同样的事情.像UIViews这样的高级选项依赖于CoreGraphics和OpenGL等低级选项,只需将它们包含在代码中以便于您使用.

如果你最终要重写UIView,请不要使用CoreGraphics.但是,只要您在一个视图中完成所有绘图,就可以通过使用CoreGraphics获得一些速度,但它真的值得吗?我找到的答案通常是否定的.当我第一次开始游戏时,我正在使用iPhone 3G.随着我的游戏越来越复杂,我开始看到一些滞后,但是对于较新的设备,它完全不明显.现在我有很多动作,在iPhone 4上玩最复杂的级别时,唯一的延迟似乎是1-3 fps的下降.

我仍然决定使用Instruments来找到占用时间最多的功能.我发现这些问题与我对UIViews的使用无关.相反,它反复调用CGRectMake进行某些碰撞感知计算,并分别为使用相同图像的某些类加载图像和音频文件,而不是从一个中央存储类中抽取它们.

所以最后,你可能可以通过使用CoreGraphics获得轻微的收益,但通常它不值得或根本没有任何效果.我使用CoreGraphics的唯一时间是绘制几何形状而不是文本和图像.

  • 我认为你可能会将Core Animation与Core Graphics混淆.Core Graphics非常慢,基于软件的绘图,除非您覆盖drawRect方法,否则不会用于绘制常规UIViews.核心动画是硬件加速,快速,并负责您在屏幕上看到的大部分内容. (8认同)