标题有点吓人,但是对于drawRect
的评价倒是一点都不过分。在平日的开发中,随意覆盖drawRect
方法,稍有不慎就会让你的程序内存暴增。下面我们来看一个例子。
去年的某天午后,北京的雾霾依旧像现在这样醇厚,我的同事辉哥像往常一样与我楼下约烟。我见辉哥表情凝重,便询问究竟。辉哥做了一个画板功能,但是苦于内存问题一直得不到解决。画板功能很简单,就是记录手指触摸的轨迹然后绘制在屏幕上。下面我们来看一张效果图:
如图我们看到左侧内存的状况随着手指的绘制逐渐恶化。另外细心的同学可以观察到,点击图中蓝色矩形按钮之后,便会弹出画板,而这时并没有进行任何的手指绘制,内存就突变为 114 MB ,然后每当手指绘制开始时,内存立即增加到 300 MB 左右稳定下来。对于正常的 iOS App 来讲,这么大的内存消耗是不能容忍的。
下面分析一下原因:
可能的原因有两个,一是在手指绘制的过程中创建的大量点对象没有及时释放或者其他资源没有及时释放。
二是系统在绘制的过程中开始大量消耗内存。
第一个原因,手指绘制的过程中创建的大量点对象没有及时释放或者其他资源没有及时释放。这一点我们暂时排除以节省时间,因为这个画板功能工程是用ARC
写的,并且我们已经做过代码检查和使用Instruments
工具来检测内存使用情况,这里并没有所谓的对象没有及时释放的问题存在。
第二个原因,系统在绘制的过程中开始大量消耗内存。首先我们曾经注意到一个诡异并且不寻常的事情就是,当黄色的画板刚刚弹出的时候内存就瞬间从 18MB 暴增至 114MB 。这一点更加说明第一个原因不是问题所在,因为这时手指还没有进行任何绘制,也就是说不存在任何点与线的对象,那么内存怎么会暴增呢?
这时我们要考虑这个画板功能是如何实现的,画板分为两步,第一步记录用户手指的轨迹,这一步会生成大量点的对象(已排除嫌疑)。第二步绘制到视图或者图层上,我们平常使用频繁的绘图方式基本上是 Quarz2D 的那套 C 语言框架,而绘制代码所在的地点在哪呢?我们今天的主角终于上场了--drawRect
。
下面我们来看一段画板功能绘制的代码:
- (void)drawRect:(CGRect)rect
{
if (!self.paths.count) return;
CGContextRef ctx = UIGraphicsGetCurrentContext();
for (BHBPaintPath *path in self.paths) {
CGContextSaveGState(ctx);
[[UIColor blackColor] set];
[path stroke]; // 关键的一步绘制
CGContextRestoreGState(ctx);
}
}
去掉绘图上下文栈和其余判断边界的代码,我们只是在当前view
上绘制了n
条黑色的线。看起来普普通通的绘图方式,怎么会导致内存的剧增呢?我们现在说罪魁祸首是drawRect
证据并不充分。我们回想画板刚弹出时的内存状况,接下来我们注释掉drawRect
所有的代码。运行的效果图如下: