`
mixer_a
  • 浏览: 367098 次
社区版块
存档分类
最新评论

iOS指南系列:如何解决内存问题 深入调试 结尾篇

阅读更多

iOS指南系列:如何解决奔溃问题

iOS指南系列:如何解决奔溃问题-关于内存访问

iOS指南系列:如何解决奔溃问题-关于内存访问续

iOS指南系列:如何解决奔溃问题-关于内存访问续

iOS指南系列:如何解决奔溃问题-深入调试

iOS指南系列:程序运行非我所设想:tableview

iOS指南系列:如何解决内存问题 深入调试 结尾篇

Zombies!

接着上次修改后的程序,运行下,还是crash,关于内存的 EXC_BAD_ACCESS ,幸运的是调试器直接定位了代码行tableView:cellForRowAtIndexPath:

EXC_BAD_ACCESS error on cellForRowAtIndexPath.

EXC_BAD_ACCESS 奔溃表明在你的代码中内存管理存在问题. 和SIGABRT 错误不一样, 很难得到明确的信息提示. 然而在这里,调试器确实有些选项可以帮助你检查内存问题(bingo,正如译者之前所言,也有类似的page guard等调试选项,帮助内存问题尽早暴露): Zombies!

打开 scheme editor 针对特定project的(也可以从菜单product):

The Edit Scheme menu option.

选择Run action, 然后选 Diagnostics tab. 启用Enable Zombie Objects box:

Enabling the Zombie Objects diagnostic option.

运行程序command+R, 程序奔溃,得到如下信息 :

Problems[18702:f803] *** -[__NSArrayM objectAtIndex:]: message sent to deallocated instance 0x6d84980

Here’s what the Zombie Enabled tool does, in a nutshell: 简单介绍下zombie enable工具的作用:

每当你创建一个新的对象(它通过发送“malloc”消息),一大块的内存被保留并持有该对象的实例变量。当对象被释放,其引用数降为零,该内存被释放,以便其他对象可以在未来使用同一块内存区域。到目前为止,一切都很好。
然而,有时候,你仍然有指针指向现在已不存在的内存块,还是有一个有效的对象。如果你的程序的某些部分尝试使用stale的指针,应用程序将崩溃,导致EXC_BAD_ACCESS错误。
(它会崩溃,如果你是幸运的;如果你运气不好,应用程序将使用一个其他的对象并带来各种混乱,特别是如果该区域memory,在某些时候被一个新的对象覆盖,那么你就使用了错误的数据)
当zombie工具的启用,为对象的内存区域并没有在得到释放对象消息时被真实释放。相反,该内存会被标记为“undead”。如果您尝试稍后再次访问内存,应用程序可以承认自己的错误,它会中止并提示消息发送到已释放的实例的错误信息。这样可以有效帮助问题重现
所以这就是这里所发生的事情。这是一个undead对象的行:

	cell.textLabel.text = [list objectAtIndex:indexPath.row];

结合下面一行信息,可以基本判断出问题的对象是NSArray list

-[__NSArrayM objectAtIndex:]

undead对象的类是_ NSArrayM,如果你已经Cocoa编程的一段时间,你知道一些基础类,如NSString和NSArray,实际上是类集群”,即原始类是 - NSString或NSArray的 - 被一个特殊的内部类取代。所以在这里,你可能会寻找一些NSArray的类型的对象,这是“list”(是一个定义为NSMutableArray类型的变量)。

如果你想确认下,可以通过nslog来判断list,当然也可以判断该变量的内存地址是否就是错误信息中提到的

NSLog(@"list is %p", list);

通过%p,可以打印出指针的地址 (在我的测试中地址是 0x6d84980, 但你的测试结果往往显示不同的地址,我们的目的是判断该地址和错误信息的地址是否一致).

 

(lldb) p list

Note: Unfortunately, this doesn’t appear to work properly for me with Xcode 4.3. For some reason, the address always shows up as 0×00000001, probably because of the class cluster.

With the GDB debugger, however, this works fine, and the variables pane in the debugger even points out that “list” is the zombie. So I’m assuming this is a bug in LLDB.

The GDB debugger points out which object is the zombie.

我们来检查下list的初始化分配代码 initWithCoder: 目前是这样的:

		list = [NSMutableArray arrayWithCapacity:10];

由于当前项目没有启用 ARC (Automatic Reference Counting) –这表示我们需要自己管理变量/内存 – 如果在后续中使用,需要retain 这个变量:

// in initWithCoder:
		list = [[NSMutableArray arrayWithCapacity:10] retain];

To prevent a memory leak, you also have to release the object in dealloc as follows:

- (void)dealloc
{
	[list release];
	[super dealloc];
}

再次运行程序。它仍然崩溃在同一行上,但是请注意,调试输出已改变:

Problems[8266:f803] array contents: (
    One,
    Two,
    Three,
    Four,
    Five
)

这意味着该阵列已正确分配和它包含的字符串。崩溃也不再是 EXC_BAD_ACCESS,但发出SIGABRT,你再挂异常断点,这就是debugger的工作:解决一个问题,找到另一个。 : - ]

Note: Even though such memory management-related errors are largely a thing of the past with ARC, you can still make your code crash on EXC_BAD_ACCESS errors, especially if you’re using unsafe_unretained properties and ivars.

My tip: Whenever you get an EXC_BAD_ACCESS error, enable Zombie Objects and try again.

Note that you shouldn’t leave Zombie Objects enabled all the time. Because this tool never deallocates memory, but simply marks it as being undead, you end up leaking all over the place and will run out of free memory at some point. So only enable Zombie Objects to diagnose a memory-related error, and then disable it again.

Stepping Through the App

Use a breakpoint to figure out this new problem. Put it on the line that is crashing:

Setting the breakpoint on cellForRowAtIndexPath.

 这个问题通过断点以及连续调试,很容易得到结论,前几个cell没有问题,直到最后一个cell

按继续执行程序“按钮数次。在某一点上,应用程序崩溃,提示以下消息:

Problems[12540:f803] *** Terminating app due to uncaught exception 'NSRangeException', 
reason: '*** -[__NSArrayM objectAtIndex:]: index 5 beyond bounds [0 .. 4]'
*** First throw call stack:
. . . and so on . . .

如果通过 po来检查indexPath 对象, 你会发现:

(lldb) po indexPath
 
(NSIndexPath *) $11 = 0x06a8a6c0 <NSIndexPath 0x6a8a6c0> 2 indexes [0, 5]

节指数仍然是0,但行指数为5。请注意该错误消息也说,“索引”。由于从0开始计数,指数5,其实就是第六行。但是,在数据模型中只有五个项目!显然,认为有更多的行比实际上是表视图。
罪魁祸首,当然是下面的方法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return 6;
}

一般而言我们会写成如下的代码,适应不同的datasource变化

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return [list count];
}

删除或禁用断点,并再次运行应用程序。最后,表视图显示,有没有更多的崩溃!

Note: The “po” command is very useful for inspecting your objects. You can use it whenever your program is paused in the debugger, either after hitting a breakpoint or after it has crashed. You do need to make sure that the correct method is highlighted in the call stack, otherwise the debugger won’t be able to find the variable.

You can also see these variables in the debugger’s left pane, but often what you see there might take a bit of deciphering to figure out:

The debugger shows the content of your variables.

Once More, With Feeling

这样,程序就完全好了么?well,几乎是的,但当我swap某个cell(无论左右/右左),出现删除按钮后,点击会出现crash,断在代码tableView:commitEditingStyle:forRowAtIndexPath:.

Swipe-to-delete will make the app crash.

The error message is:

Problems[18835:f803] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], 
/SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1046

That looks like it comes from UIKit, not from the app’s code. Type “c” a few times to throw the exception so you get a more useful error message:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', 
reason: 'Invalid update: invalid number of rows in section 0.  The number of rows 
contained in an existing section after the update (5) must be equal to the number 
of rows contained in that section before the update (5), plus or minus the number 
of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or 
minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
*** First throw call stack: . . .

啊,你去那里。这是不言自明。应用程序告诉表视图行被删除,但有人忘了从数据模型中删除。因此,表视图中没有看到任何变化。修复方法如下:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
	if (editingStyle == UITableViewCellEditingStyleDelete)
	{
		[list removeObjectAtIndex:indexPath.row];
		[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
	}   
}

好极了!采取了一些努力,但你终于有了一个不崩溃的应用程序。 : - ]

Where to Go From Here?

Some key points to remember:

If your app crashes, the first thing is to figure out exactly where it crashed and why. Once you know these two things, fixing the crash is often easy. The debugger can help you with this, but you need to understand how to make it work for you.

Some crashes appear to happen randomly, and these are the tough ones, especially when you’re working with multiple threads. Most of the time, however, you can find a consistent way to make your app crash every time you try it.

If you can figure out how to reproduce the crash with a minimal number of steps, then you’ll also have a good way to verify that the bug was fixed (i.e. it won’t happen again). But if you cannot reliably reproduce the error, then you can never be certain that your changes have made it go away.

Tips:

  • If the app crashes on main.m, then set the Exception Breakpoint.
  • With the Exception Breakpoint enabled, you may no longer get a useful error message. In that case, either resume the app until you do, or type the “po $eax” command after the debug prompt.
  • If you get an EXC_BAD_ACCESS, enable Zombie Objects and try again.
  • The most common reason for crashes and other bugs are missing or bad connections in your nibs or storyboards. These usually don’t result in compiler errors and may therefore be hidden from sight.
  • Don’t ignore compiler warnings. If you have them, they’re often the reason why things go wrong. If you don’t understand why you get a certain compiler warning, then figure that out first. These are life savers!
  • Debugging on the device can be slightly different from debugging on the simulator. These two environments are not exactly the same and you’ll get different results.

     

    For example, when I ran the Problems app on my iPhone 4, the very first crash happened in the NSArray initialization because of the missing nil sentinel, and not because the app was callingsetList: on the wrong view controller. That said, the same principles apply for finding the root causes of your crashes.

And don’t forget the static analyzer tool, which will catch even more mistakes. If you’re a beginner, I recommend that you always enable it. You can do this from the Build Settings panel for your project:

Setting the static analyzer to run on each build.

以上的原文总结非常好,翻译过来肯定不到位!

开开心心debugging! :-]

译者总结:

1.zoombie是有用的,但最终版本一定要disable(这个设置是项目级别的),否则程序性能问题

2.po ¥eax po variableSymbol p variablesymbol 基本的llbg调试指令:例如c

3.target build settings使用static analyzer来帮助分析代码潜在问题

分享到:
评论

相关推荐

    iOS开发指南:从零基础到App Store上架(第3版)源代码(第一部分)

    iOS开发指南:从零基础到App Store上架(第3版)从这书入手swift也应该是个不错的选择,这里先奉上源码。 源码总共分为两部分(大概194M-压缩),请分别下载。下载之后用RAR先解压缩一遍,得到后缀为7z_的文件,然后...

    iOS 编程基础:Swift、Xcode 和 Cocoa 入门指南

    通过阅读本书,你将学习 Swift 面向对象的概念、理解如何使用 Apple 的开发工具,以及探索 Cocoa 是如何提供 iOS 应用所需的底层功能的。

    iOS内存泄漏调试工程

    本文将深入探讨“iOS内存泄漏调试工程”中的核心知识点,并以提供的AsyncImageTableviewDemo为例进行讲解。 1. **内存管理机制**: iOS采用自动引用计数(ARC)来管理内存,当一个对象的引用计数变为0时,系统会...

    ios加载图片内存暴涨解决方法

    本篇文章将深入探讨如何解决"ios加载图片内存暴涨"的问题,主要介绍三种策略:第一种是使用UIKit的`setImage`方法,第二种是利用苹果官方推荐的分片比例裁剪方式,最后是采用CATiledLayer进行优化。 1. **UIKit的...

    iOS编程基础:Swift、Xcode和Cocoa入门指南 EPUB

    iOS编程基础:Swift、Xcode和Cocoa入门指南 (O’Reilly精品图书系列) iOS编程基础:Swift、Xcode和Cocoa入门指南 (O’Reilly精品图书系列) iOS编程基础:Swift、Xcode和Cocoa入门指南 (O’Reilly精品图书系列)

    IOS开发指南(第5版) pdf下载地址

    《iOS开发指南(第5版)》是一本深入探讨iOS应用程序开发的专业书籍,旨在帮助开发者从零基础到熟练掌握Apple的移动操作系统上的应用构建过程。该书第五版更新了最新的开发技术和工具,确保读者能够使用最新的Xcode和...

    IOS内存管理与软件调试

    在iOS应用开发中,内存管理和软件调试是两个至关重要的环节,它们直接影响到应用程序的性能、稳定性和用户体验。本文将深入探讨这两个主题,并提供一些实用的技巧和方法。 首先,我们来谈谈iOS内存管理。iOS系统...

    iOS开发指南:从零基础到App Store上架-第2章

    iOS开发指南:从零基础到App Store上架 第2章 由于全新问题,只能分开传。

    Xcode iOS 16真机调试包 开发调试

    本压缩包提供了针对iOS 16的真机调试资源,帮助开发者进行iOS 16适配和应用开发的调试工作。 首先,"Xcode iOS 16真机调试包"是指包含有支持iOS 16版本的模拟器和设备配置的Xcode版本,使得开发者可以在真实设备上...

    iOS16.5-真机调试包

    本文将深入探讨“iOS16.5-真机调试包”这一主题,以及如何利用它进行有效的iOS应用调试。 首先,我们需要了解iOS16.5是苹果操作系统的一个更新版本。每个新版本通常会引入新的功能、性能改进和安全修复。对于开发者...

    iOS开发指南:从零基础到App Store上架-第一章

    在本章"iOS开发指南:从零基础到App Store上架"中,我们将开始一段激动人心的旅程,学习如何从无到有地构建一个iOS应用并将其发布到App Store。对于初学者而言,iOS开发可能显得有些陌生和复杂,但通过逐步的指导,...

    iOS11.2 真机调试包

    在iOS开发过程中,真机调试是一项至关重要的环节,它允许开发者在实际的设备上测试应用程序,确保软件在各种硬件配置和系统版本上的表现。这里我们关注的是“iOS11.2 真机调试包”,这是一份专为在iOS 11.2系统上...

    iOS11.1 真机调试包

    综上所述,"iOS11.1真机调试包"是iOS开发中的关键组件,它使得开发者能够在真实设备上进行深入的测试和调试,从而提高应用的质量和用户体验。正确理解和使用这个调试包,对于iOS 11.1平台的软件开发至关重要。

    IOS16.3真机调试包

    本文将深入探讨“iOS 16.3真机调试包”,以及如何利用Xcode进行有效的真机调试。 **iOS 16.3版本更新** iOS 16.3是苹果公司发布的一个系统更新,旨在修复已知问题、增强系统性能和安全性,并可能引入新的功能或改进...

    iOS11.3真机调试包

    在iOS开发过程中,真机调试是一项至关重要的环节,它允许开发者在实际的iOS设备上运行应用程序,以便测试其性能、兼容性和用户体验。本资源“iOS11.3真机调试包”是专为iOS 11.3版本设计的,用于帮助开发者在该系统...

    iOS 13.5 真机调试包

    在iOS开发过程中,真机调试是一项至关重要的环节,它允许开发者在实际设备上测试应用程序,以确保软件在各种硬件配置和真实使用环境下运行无误。iOS 13.5是苹果公司发布的一个重要版本,引入了许多新功能和性能优化...

    IOS11.1调试支持文件

    本文将深入探讨iOS 11.1调试支持文件的内涵、作用以及如何使用。 首先,理解"Xcode iOS 11.1 support file"的概念至关重要。这是一组由Apple为Xcode设计的特殊组件,旨在增强其对iOS 11.1系统的兼容性和调试能力。...

    Pro iOS 5 Tools: Xcode Instruments and Build Tools

    书中通过一个购物车应用的例子,介绍了如何从创建项目到使用分布式版本控制系统设置环境的全过程,并深入探讨了如何优化应用性能、诊断并解决内存问题、处理网络问题以及减少设备功耗等方面的内容。 #### 详细知识...

    ios14.7真机调试包

    "ios14.7真机调试包"指的是针对iOS 14.7版本的设备进行应用调试所需的一系列配置和工具。iOS 14.7是苹果公司对移动操作系统的一次更新,带来了多项改进和新功能,而Xcode作为Apple官方的集成开发环境(IDE),是进行...

Global site tag (gtag.js) - Google Analytics