为啥drawRect的空实现:会对动画期间的性能产生不利影响

Posted

技术标签:

【中文标题】为啥drawRect的空实现:会对动画期间的性能产生不利影响【英文标题】:Why an empty implementation of drawRect: will adversely affect performance during animation为什么drawRect的空实现:会对动画期间的性能产生不利影响 【发布时间】:2013-09-11 18:08:13 【问题描述】:

我正在继承我的 UIView 类。 Xcode(我使用的是 4.6.3)自动生成的代码说,

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect

    // Drawing code

*/

它在我的脑海中提出了几个问题:

1) 为什么drawRect: 的空实现会导致动画期间的不良性能。

2) 我应该什么时候实现drawRect:

3) 如果我正在实施drawRect:,那么应该采取什么措施作为最佳实践的预防措施。

【问题讨论】:

2) “仅覆盖 drawRect:如果您执行自定义绘图”就在评论中 The question: "to drawRect or not to drawRect (when should one use drawRect/Core Graphics vs subviews/images and why?)" 可能会回答(甚至可能重复)您的问题 @DavidRönnqvist 感谢您提供建议的链接。它涵盖了我的问题的所有方面。 【参考方案1】:

要知道何时使用 -drawRect: 以及何时使用 UIImageView 之类的事情,我需要再解释一下:

UIView 和 CGLayer 本质上是处理固定图像。这些图像被上传到显卡(如果你了解 OpenGL,可以将图像视为纹理,将 UIView/CGLayer 视为显示此类纹理的多边形)。一旦图像在 GPU 上,它可以非常快速地绘制,甚至可以绘制多次,并且(具有轻微的性能损失)即使在其他图像之上具有不同级别的 Alpha 透明度。

CoreGraphics/Quartz 是一个用于生成图像的 API。它需要一个像素缓冲区(同样,想想 OpenGL 纹理)并更改其中的单个像素。这一切都发生在 RAM 和 CPU 中,只有在 Quartz 完成后,图像才会“刷新”回 GPU。这种从 GPU 获取图像、对其进行更改、然后将整个图像(或至少是其中相当大的一部分)上传回 GPU 的往返过程相当缓慢。此外,Quartz 进行的实际绘图虽然对您所做的工作来说非常快,但比 GPU 的工作要慢得多。

这很明显,考虑到 GPU 主要是在大块中围绕未更改的像素移动。 Quartz 随机访问像素并与网络、音频等共享 CPU。此外,如果您同时使用 Quartz 绘制多个元素,则必须在其中一个更改时重新绘制所有元素,然后上传整个块,而如果您更改一个图像,然后让 UIViews 或 CGLayers 将其粘贴到您的其他图像上,您可以将更少的数据上传到 GPU。

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

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

所以最快的绘制方法通常是将静态内容与变化的内容分开(在单独的 UIViews/UIView 子类/CGLayers 中)。将静态内容加载为 UIImage 并使用 UIImageView 进行绘制,并将运行时动态生成的内容放入 drawRect 中。如果您有重复绘制的内容,但其本身并没有改变(即,在同一插槽中显示 3 个图标以指示某种状态)也可以使用 UIImageView。

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

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

【讨论】:

【参考方案2】:

Only override draw() if you perform custom drawing.

这意味着如果我们不执行自定义绘图,我们不应该编写覆盖函数。使用“默认”draw()。所有图形(如果有的话)都是“本地”处理的,因此性能得到了最佳优化。

An empty implementation adversely affects performance during animation.

如果我们做override draw(),但我们没有在函数中编写任何代码,让这个func为空,那就是(1)override draw()实现,它也是( 2) empty 也是一个。这会降低性能。以下代码

public override func (_ r: CGRect) 
    // empty

在动画期间效率不高。大概系统会尝试重绘整个画布而不是只重绘受影响的部分?

【讨论】:

以上是关于为啥drawRect的空实现:会对动画期间的性能产生不利影响的主要内容,如果未能解决你的问题,请参考以下文章

iOS:为啥 UIView 的 drawRect 比 CALayer 的 drawInContext 性能好

为啥动画自定义 CALayer 属性会导致其他属性在动画期间为零?

使用 drawRect、setNeedsDisplay 和 layoutSubViews

drawRect 适合动画代码吗?

我想动画,事实上淡化,“内”drawRect

CALayer 和 drawRect