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

时间:2013-02-02 07:03:26

标签: iphone ios core-graphics

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

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

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

公平地说,这是在3GS非常新品牌时写的,iDevices从那以后变得更快。 this method上的regularly suggested仍为interwebs,而其他地方则为高性能表格。事实上,这是Apple's Table Sample Code中建议的方法,已在多个WWDC视频(Practical Drawing for iOS Developers)和许多iOS programming books中提出过建议。

甚至awesome looking tools设计图形并为它们生成核心图形代码。

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

但是一旦我认为我得到了“尽可能支持核心图形”的想法,我开始看到drawRect经常导致应用程序响应能力差,内存非常昂贵,并且真正对CPU征税。基本上,我应该“Avoid overriding drawRect”(WWDC 2012 iOS App Performance: Graphics and Animations

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

我发现使用Core Graphics有几个明显的情况:

  1. 您有动态数据(Apple的股票图表示例)
  2. 您有一个灵活的UI元素,无法使用简单的可调整大小的图像执行
  3. 您正在创建一个动态图形,一旦渲染在多个地方使用
  4. 我看到了避免Core Graphics的情况:

    1. 您的视图属性需要单独设置动画
    2. 您的视图层次结构相对较小,因此任何使用CG的额外努力都不值得获益
    3. 您希望更新视图的各个部分而不重绘整个内容
    4. 当父视图大小更改时,子视图的布局需要更新
    5. 所以赐予你的知识。您在什么情况下可以使用drawRect / Core Graphics(也可以通过子视图完成)?哪些因素会导致您做出这个决定?如何/为什么在一个自定义视图中绘制建议用于黄油平滑表格单元格滚动,但Apple建议drawRect反对性能原因一般?那么简单的背景图片(你何时使用CG创建它们与使用可调整大小的png图像)?

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

      问题更新

      感谢所有人的信息。这里有一些澄清问题:

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

4 个答案:

答案 0 :(得分:70)

尽可能坚持使用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优化变得不那么重要了。因此,瓶颈就变成了你可以优化核心图形绘制的程度。

无论何时,只要使用UIKit即可。做最简单的实现。轮廓。当有激励时,优化。

答案 1 :(得分:51)

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

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

这很明显,考虑到GPU主要是在大块的未更改像素中移动。 Quartz对像素进行随机访问,并与网络,音频等共享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应用程序以不同的方式告诉您。

答案 2 :(得分:26)

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

一般方法

很明显,Ben Sandofsky在his answer中提到的一般方法应该是“无论何时你都可以使用UIKit。做最简单的实现是有效的。简介。当有激励时,优化。“

为什么

  1. iDevice中存在两个主要可能的瓶颈, CPU和GPU
  2. CPU负责视图的初始绘制/渲染
  3. GPU负责大部分动画(核心动画),图层效果,合成等。
  4. UIView内置了许多用于处理复杂视图层次结构的优化,缓存等
  5. 当覆盖drawRect时,你会错过UIView提供的许多好处,而且通常比让UIView处理渲染要慢。
  6. 在单个平面中绘制单元格内容UIView可以极大地提高滚动表格上的FPS。

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

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

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

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

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

    总而言之,在一个视图中使用核心图形绘制单元格的原因可以加快表格滚动速度并不是因为它的绘制速度更快,而是因为它减少了GPU上的负载,这是瓶颈你在那种特殊情况下遇到麻烦。

答案 3 :(得分: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的唯一时间是绘制几何形状而不是文本和图像。