drawRect或不drawRect(何时应该使用drawRect / Core Graphics vs子视图/图像,为什么?)

澄清这个问题的目的:我知道如何用两个子视图和使用drawRect创build复杂的视图。 我试图完全理解何时以及为什么要使用一个。

我也明白,提前优化是没有意义的,而在做任何分析之前,要做一些更困难的事情。 考虑到我对这两种方法都很满意,现在真的想要更深入的了解。

很多我的困惑来自于学习如何使桌面视图滚动性能真正平滑和快速。 当然这个方法的原始来源是来自iPhone 后面的作者 (以前tweetie)的作者。 基本上说,为了让桌面滚动顺滑,秘诀是不使用子视图,而是在一个自定义的uiview中做所有的绘图。 从本质上讲,使用大量的子视图似乎减慢了渲染速度,因为它们有很多开销,并且不断地在其父视图上重新合成。

公平的说,这是在3GS是新品牌的时候写的,iDevices从那以后变得更快了。 这种方法在互联网和其他地方也经常被 build议用于高性能表格。 事实上,这是苹果公司的示例代码中的一个build议的方法,已经在几个WWDCvideo( 适用于iOS开发人员的实用绘图 )和许多iOS 编程书籍中被提出 。

甚至有令人敬畏的工具来devisegraphics,并为它们生成Core Graphics代码。

所以一开始我就会相信“Core Graphics存在的原因是很快的,这很快!

但是,只要我认为我的想法是“尽可能地支持核心graphics”,我开始发现drawRect经常对应用程序的反应不佳负有责任,这是非常昂贵的内存明智的做法,并且真正对CPU征税。 基本上,我应该“ 避免重写drawRect ”(WWDC 2012 iOS应用程序性能:graphics和animation )

所以我想,就像一切,这是复杂的。 也许你可以帮助自己和其他人了解什么时候使用drawRect?

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

  1. 你有dynamic数据(苹果股票图例)
  2. 你有一个灵活的UI元素,不能用一个简单的可resize的图像执行
  3. 您正在创build一个dynamicgraphics,一旦呈现在多个地方使用

我看到情况,以避免核心graphics:

  1. 您的视图的属性需要单独设置animation
  2. 你有一个相对较小的视图层次,所以使用CG所感知的额外努力是不值得的
  3. 你想要更新视图的片断而不用重绘整个事物
  4. 父视图大小更改时,需要更新子视图的布局

所以赋予你的知识。 在什么情况下你达到drawRect / Core Graphics(这也可以通过子视图来完成)? 什么因素导致你做出这个决定? 如何/为什么在一个自定义视图中绘制推荐用于平滑表格单元格滚动,但是苹果build议通常针对性能原因使用drawRect? 简单的背景图像(什么时候用CG创build它们,使用可resize的PNG图像)?

对这个主题的深入理解可能不需要做出有价值的应用,但是我不喜欢在不能解释原因的情况下select技术。 我的大脑对我生气。

问题更新

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

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

坚持到UIKit和子视图,只要你可以。 您可以提高生产力,并利用所有应更容易维护的面向对象机制。 当你无法从UIKit获得所需的性能时使用Core Graphics,或者你知道在UIKit中一起破解绘图效果会更复杂。

一般的工作stream程应该是build立子视图的表格。 使用工具来测量您的应用将支持的最旧硬件上的帧频。 如果你不能得到60fps,下降到CoreGraphics。 当你这样做了一段时间,你会明白UIKit何时可能是浪费时间。

那么,为什么Core Graphics速度很快?

CoreGraphics不是很快。 如果它一直在使用,你可能会变慢。 这是一个丰富的绘图API,它需要在CPU上完成工作,而不是将大量的UIKit工作转移到GPU上。 如果您必须在屏幕上移动一个球,那么在每秒60次的视图上调用setNeedsDisplay将是一个糟糕的主意。 所以,如果你的视图的子组件需要单独的animation,每个组件应该是一个单独的图层。

另一个问题是,当你不使用drawRect进行自定义绘图时,UIKit可以优化股票视图,所以drawRect是一个无操作,或者它可以采用合成的快捷方式。 当你重写drawRect的时候,UIKit必须采取缓慢的path,因为它不知道你在做什么。

在表格视图单元的情况下,这两个问题可以超过好处。 当视图第一次出现在屏幕上时,drawRect被调用后,内容被caching,滚动是由GPU执行的一个简单的翻译。 因为你正在处理一个单一的视图,而不是一个复杂的层次结构,UIKit的drawRect优化变得不那么重要。 所以瓶颈成为你可以优化你的Core Graphics绘图。

只要你可以,使用UIKit。 做最简单的实现。 简介。 当有激励,优化。

不同的是,UIView和CALayer基本上处理固定的图像。 这些图像上传到graphics卡(如果你知道OpenGL,将图像看作纹理,将UIView / CALayer作为显示这种纹理的多边形)。 一旦图像在GPU上,即使在其他图像顶部的alpha透明度不同的情况下,图像也可以非常快速地被绘制,甚至几次,并且(轻微的性能损失)。

CoreGraphics / Quartz是一个用于生成图像的API。 它需要一个像素缓冲区(再次,考虑OpenGL纹理),并改变其中的个别像素。 这一切都发生在RAM和CPU上,并且只有Quartz完成后,图像才会被刷新回GPU。 这种从GPU获取图像,改变图像,然后将整个图像(或者至less相对较大的一部分)上传到GPU的往返行程相当缓慢。 另外,Quartz所做的实际绘制,虽然对于你正在做的事情来说真的很快,但比GPU所做的要慢。

这是显而易见的,考虑到GPU大部分是大块未变化的像素。 Quartz对象素进行随机访问,并与networking,audio等共享CPU。另外,如果你有几个你同时使用Quartz绘制的元素,你必须在修改时重新绘制所有元素,然后上传整个大块,而如果你改变一个图像,然后让UIViews或CALayers粘贴到其他图像,你可以上传less量的数据到GPU。

当你不实现-drawRect时,大多数视图可以被优化掉。 他们不包含任何像素,所以不能画任何东西。 其他视图,如UIImageView,只绘制一个UIImage(同样,它本质上是对已经加载到GPU上的纹理的引用)。 所以如果使用UIImageView绘制相同的UIImage 5次,它只会上传到GPU一次,然后在5个不同的位置绘制到显示器,为我们节省了时间和CPU。

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

所以最快速的绘制方式通常是将静态内容与改变内容分开(在单独的UIViews / UIView子类/ CALayers中)。 将静态内容作为UIImage加载,并使用UIImageView绘制,并将在运行时dynamic生成的内容放在drawRect中。 如果您的内容反复绘制,但本身不会改变(即3个图标显示在同一个插槽中以指示某种状态),请使用UIImageView。

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

如果你的内容是在运行时dynamic生成的,但是在应用程序的生命周期中保持不变(例如一个包含用户名的标签),那么使用Quartz来绘制整个事物实际上是有意义的,button边框等,作为背景的一部分。 但是这通常是一个不需要的优化,除非仪器应用程序以不同的方式告诉你。

我要试着总结一下我从其他人的答案中推断出的内容,并在原始问题的更新中澄清问题。 但是,我鼓励别人保留答案,把那些提供了好的信息的人投票。

一般的做法

很显然,正如Ben Sandofsky在他的回答中提到的那样,一般的方法应该是尽可能使用UIKit,做最简单的实现,简介,当有激励时,优化”。

为什么

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

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

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

事实上,Core Graphics可能比初始渲染的嵌套UIView层次结构慢。 然而,似乎滚动波涛汹涌的典型原因是你瓶颈GPU,所以你需要解决这个问题。

为什么重写drawRect(使用核心graphics)可以帮助表滚动:

据我所知,GPU不负责视图的初始渲染,而是在渲染完成后将纹理或位图有时与某些图层属性交给使用者。 然后它负责合成位图,渲染所有这些图层效果,以及大部分animation(Core Animation)。

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

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

我是一名游戏开发人员,当我的朋友告诉我,基于UIImageView的视图层次结构会降低我的游戏速度并使其变得糟糕时,我也在问同样的问题。 然后我开始研究我能find的所有关于是否使用UIViews,CoreGraphics,OpenGL或Cocos2D等第三方的东西。 我从WWDC的朋友,老师和苹果工程师得到的一致答案是,最终没有什么区别,因为在某种程度上他们都在做同样的事情 。 像UIViews这样的更高级的选项依赖于像CoreGraphics和OpenGL这样的低级选项,只是它们被包装在代码中以便于你使用。

如果你只是想重写UIView,不要使用CoreGraphics。 但是,使用CoreGraphics可以获得一些速度,只要你在一个视图中完成所有绘图,但真的值得吗? 我find的答案通常不是。 当我第一次开始我的游戏时,我正在使用iPhone 3G。 随着我的游戏越来越复杂,我开始看到一些滞后,但是对于新设备来说,这是完全不明显的。 现在我有很多动作在进行,唯一的延迟似乎是在iPhone 4上以最复杂的水平进行的1-3fps的下降。

尽pipe如此,我仍然决定使用“乐器”来查找占用最多时间的function。 我发现这些问题与我使用UIViews无关 。 而是反复调用CGRectMake进行某些碰撞感应计算,并为使用相同图像的某些类别分别加载图像和audio文件,而不是从一个中央存储类中提取图像和audio文件。

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