iOS中查找内存问题

来源:互联网 发布:网络top图绘制工具 编辑:程序博客网 时间:2024/05/18 03:47

come from: http://wiki.eoe.cn/page/iOS_pptl_artile_30458.html

许多性能问题最终都归结为内存问题。如果你看到了意外的内存提醒,那么最好先检查一下这些问题。使用Instruments中的Allocations模板。图20-1显示了结果。

enter image description here

图20-1 Allocations Instruments

我们看看这个图,这个应用中内存分配很明显已经失控了。内存使用在持续增长。对Live
Bytes列进行排序后,可以看到消耗内存最多的是UILabel对象,一分钟过后其中7000多个对象就已经完成分配了。这点非常可疑。点击UILabel旁边的箭头查看更详细的信息,按下Cmd+E可以显示Extended
Detail(参见图20-2)。

enter image description here

图20-2 Allocation的Extended Detail

这里你能看到UILabel是在–[ViewController appendNextCharacter]中创建的。如果你双击那个栈帧,就会看到那段代码带有热点着色,说明每行代码消耗了多少时间(参见图20-3)。

enter image description here

图20-3 代码视图

稍加分析就能发现问题。每次调用这个方法时,它会重新创建所有的标签,而不是只创建一个新标签。你只要删掉那个循环就可以了,如下所示。

ZipTextView2.m(ZipText)

1
for (NSUInteger i = 0; i <= self.index; i++)

用下面的赋值表达式替换它就可以了:

1
NSUInteger i = self.index;

现在,重新运行Instruments时,内存使用情况就会好很多。一分钟后,你使用的内存还不到2
MB,而不是之前的9
MB多,但内存使用随着时间稳定地增长,这依然是个问题。在这个例子中,很明显问题在于UILabel视图,但这是一个演示使用堆快照分析方法的绝好机会。再次用Allocations工具启动ZipText,若干秒后按下Mark
Heap按钮。让它运行一会儿后,再次按下Mark
Heap按钮。它会显示在这两个时间点之间创建的、尚未销毁的所有对象。这是定位执行某一操作时泄漏了哪些对象的绝妙方法。图20-4显示过了大约6秒,我们创建了148个新的UILabel对象。

enter image description here

图20-4 改进后的内存管理

这时,你应该重新评估是否要继续为每个字符使用一个单独的UILabel。也许在这里一个单独的UILabelUITextView甚至是一个自定义的drawRect:都可能是更好的选择。ZipTextView3演示了如何用自定义的drawRect:实现同样的功能。

ZipTextView3.m(ZipText)

 1 2 3 4 5 6 7 8 91011121314
- (void)appendNextCharacter {  self.index++;  [self setNeedsDisplay];}- (void)drawRect:(CGRect)rect {  for (NSUInteger i = 0; i <= self.index; i++) {    if (i < self.text.length) {      NSString *character = [self.text substringWithRange:                             NSMakeRange(i, 1)];      [character drawAtPoint:[self originAtIndex:i fontSize:kFontSize]                    withFont:[UIFont systemFontOfSize:kFontSize]];    }  }}

再运行一次,如图20-5所示,内存再也没有增长。不过现在性能变差了,慢到开始有点卡了。在开始介绍CPU性能问题之前,我们再介绍一些内存性能分析方面有用的技巧。

enter image description here

图20-5 通过自定义绘图表示ZipTextView的内存足迹

  • 点击Allocations工具旁边的小*i*可以设置各种选项。最有用的选项之一是将Track Display从Current Bytes改为Allocation Density。这幅图片显示了在采样时间段内,应用做了多少内存分配。你能发现在哪里做了过多的内存扰动(Memory Churn)。分配内存的成本很高,所以尽可能不要频繁地创建和销毁成千上万个对象。
  • Leaks工具可能偶尔也能派上用场,但不要期望过高。它只会检查未引用的内存,而不会检测循环保留,而后者是更常见的内存问题;它也不会检测没有成功释放的内存,比如前面例子中的UILabel视图。它还会有一些错误的测试结果。比如它经常会在程序启动时显示一个或两个微小的泄漏。如果每次执行操作时或者定期都会看到有新的内存泄漏,那就要对它进行分析。总而言之,堆快照是跟踪内存泄漏问题更有用的工具。
  • 记录你查看的是否是Live Bytes。Instruments会记录分配的内存总量和净分配内存。净分配内存是指分配的内存减去已释放的内存。如果你一直都是定期创建和销毁对象,那么分配的总内存会比净分配内存大好几个数量级。Instruments有时会用live或still living表示净分配内存。在主界面上(参考图20-1),最后一列中的图表用浅色条表示分配的总内存,用深色条表示净分配内存。
  • 记住,Leaks和Allocations工具只会告诉你哪些地方分配了内存。这与内存泄漏无关。分配了内存的地方并不一定就是出现内存问题的地方