使用drawRect还是不使用drawRect(何时应该使用drawRect / Core Graphics与子视图/图像进行比较,为什么?)


142

为了阐明这个问题的目的:我知道如何使用两个子视图并使用drawRect创建复杂的视图。我正在尝试完全理解何时以及为什么要使用另一个。

我还了解到,提前进行大量优化并在执行任何性能分析之前以更困难的方式进行操作是没有意义的。考虑到我对这两种方法都比较满意,现在真的想加深了解。

我的很多困惑来自于学习如何使表格视图滚动性能真正变得平滑而又快速。当然,此方法的原始来源来自iPhone的twitter(以前为tweetie)背后作者基本上,它说的是使表格平滑地滚动,其秘密是不要使用子视图,而是在一个自定义uiview中进行所有绘制。从本质上讲,似乎使用大量子视图会减慢渲染速度,因为它们具有大量开销,并且会不断在其父视图上进行重新组合。

公平地讲,这是在3GS刚起步时才写的,而iDevices从那时起变得越来越快。仍然在互连网上和其他地方经常建议使用此方法来获得高性能表。实际上,这是Apple的Table Sample Code中建议的方法,一些WWDC视频(面向iOS开发人员的实用绘图)和许多iOS 编程书籍中都建议使用该方法

甚至还有看起来很棒的工具来设计图形并为其生成Core Graphics代码。

因此,起初我会相信“存在Core Graphics的原因。它非常快!”

但是,一旦我想到“尽可能使用Favor 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(也可以通过子视图完成)?哪些因素导致您做出该决定?建议如何/为什么在一个自定义视图中进行绘制以实现黄油状的平滑表格单元滚动,但是苹果出于性能考虑而建议不建议使用drawRect?那么简单的背景图像呢(何时使用CG与可调整大小的png图像创建它们)?

制作有价值的应用程序可能不需要对此主题有深入的了解,但是我不喜欢在无法解释原因的情况下在各种技术之间进行选择。我的脑子生我的气。

问题更新

感谢大家提供的信息。一些澄清的问题在这里:

  1. 如果您正在绘制带有核心图形的东西,但是可以使用UIImageViews和预渲染的png完成相同的事情,那么您是否应该始终遵循这种做法?
  2. 一个类似的问题:尤其是使用诸如此类的badass工具时,应何时考虑在核心图形中绘制界面元素?(可能在元素的显示是可变的时。例如,具有20种不同颜色变化的按钮。还有其他情况吗?)
  3. 根据我在下面的答案中的理解,是否可以通过在复杂的UIView渲染本身之后有效地捕获单元格的快照位图,并在滚动和隐藏复杂的视图时显示该位图,来获得相同的性能?显然,必须解决一些问题。我有一个有趣的想法。

Answers:


72

尽可能使用UIKit和子视图。您可以提高工作效率,并利用所有应该易于维护的OO机制。当无法从UIKit中获得所需的性能时,请使用Core Graphics,或者您知道尝试将UIKit中的绘图效果合并在一起会更加复杂。

一般的工作流程应该是使用子视图构建表视图。使用仪器在您的应用程序支持的最旧硬件上测量帧频。如果无法获得60fps,请下拉至CoreGraphics。完成一段时间后,您会发现UIKit何时可能会浪费时间。

那么,为什么Core Graphics很快?

CoreGraphics并不是真的很快。如果一直在使用它,您可能会变慢。这是一个丰富的绘图API,它需要在CPU上完成工作,而不是将许多UIKit工作转移到GPU。如果必须为在屏幕上移动的球设置动画,那么每秒在视图上调用setNeedsDisplay可能是一个糟糕的想法,每秒60次。因此,如果您的视图子组件需要单独设置动画,则每个组件应为单独的图层。

另一个问题是,当您不使用drawRect进行自定义绘图时,UIKit可以优化库存视图,因此drawRect是无操作的,或者可以使用合成的快捷方式。当您重写drawRect时,UIKit必须走慢的路,因为它不知道您在做什么。

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

只要有可能,请使用UIKit。做最简单的实现。个人资料。有激励时,进行优化。


53

不同之处在于,UIView和CALayer本质上处理固定图像。这些图像将上载到图形卡(如果您知道OpenGL,则将图像视为纹理,将UIView / CALayer视为显示此类纹理的多边形)。一旦将图像放置在GPU上,就可以非常快速地绘制图像,甚至可以绘制数次,并且即使在其他图像之上具有不同级别的alpha透明度,也可以(有少许性能损失)绘制图像。

CoreGraphics / Quartz是用于生成图像的API 。它需要一个像素缓冲区(再次考虑OpenGL纹理)并更改其中的单个像素。这一切都发生在RAM和CPU中,只有完成Quartz后,图像才会“刷新”回GPU。从GPU获取图像,进行更改,然后将整个图像(或至少其中较大的一部分)上传回GPU的这种往返过程相当慢。而且,尽管Quartz所做的工作确实非常快,但是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。

一个警告:存在太多的UIView。特别透明的区域会给GPU带来更大的绘制负担,因为它们在显示时需要与它们后面的其他像素混合。这就是为什么您可以将UIView标记为“不透明”,以向GPU指示它可以消除该图像后面的所有内容。

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


感谢您的详细回答。希望有更多的人向下滚动并投票赞成。
Bob Spryn 2014年

希望我能两次投票赞成。感谢您的宝贵意见!
西藏沿海地区

小幅回调:在大多数情况下没有下跌从GPU的负载,但是上传仍然基本上你可以问一个GPU做最慢的操作,因为它必须从速度较慢的系统内存更快的传输大量像素缓冲区到VRAM。OTOH一旦有图像,移动它只需要向GPU发送一组新的坐标(几个字节),以便知道绘制位置。
uliwitness

@uliwitness我正在回答您的问题,您提到将对象标记为“不透明会消除该图像后面的所有内容”。您能说明一下这部分吗?
Rikh '18

@Rikh将图层标记为不透明意味着:a)在大多数情况下,GPU不会麻烦地在该图层下绘制其他图层,至少其中一部分位于不透明图层下。即使已经绘制了它们,它实际上也不会执行Alpha混合,而只是在屏幕上复制顶层的内容(通常,不透明层中的完全透明像素最终会变成黑色或至少是其颜色的不透明版本)
uliwitness

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并不负责视图的初始渲染,而是渲染后的手工纹理或位图,有时具有某些图层属性。然后,它负责合成位图,渲染所有这些图层效果以及大多数动画(核心动画)。

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

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


1
小心点。GPU有效地重新组合了每一帧的整个层树。因此,移动层实际上是零成本。如果创建一个大图层,则仅移动该图层比移动多个图层要快。此层中移动某些内容会突然涉及CPU并产生成本。由于单元格内容通常不会发生太大变化(仅响应于相对少见的用户操作),因此使用较少的视图可以加快处理速度。但是,对所有单元进行大范围查看将是Bad Idea™。
uliwitness 2014年

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的时间是绘制几何形状而不是文本和图像时。


8
我认为您可能将Core Animation与Core Graphics混淆了。Core Graphics确实很慢,是基于软件的绘图,除非覆盖了drawRect方法,否则不会用于绘制常规UIView。Core Animation是硬件加速的,快速的,并且负责您在屏幕上看到的大多数内容。
尼克·洛克伍德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.