应用程序崩溃定位查找 (一)

来源:互联网 发布:苹果下载铃声软件 编辑:程序博客网 时间:2024/05/21 22:22

它发生在我们最好的: 你工作愉快地在您的应用程序,一切都很好,然后突然 — — 噗!— — 它的崩溃。收纳!!(线索悲伤小提琴)。

第一件事是: ,不要惊慌!

固定崩溃并不需要硬。你可能会进一步恶化的情况,如果你抓狂,开始随意改变一些东西,希望该 bug 会神奇地消失如果只你说出正确的咒语。相反,您需要采取系统的方法和学习如何您的方式通过一个崩溃的原因。

第一要务是找出确切地在您的代码下崩溃发生: 在哪个文件和哪些行。Xcode 调试器会帮助你这一点,但您需要了解如何善用它,而这正是本教程将告诉你!

本教程是为所有的开发人员,从初级到高级。即使你是经验丰富的 iOS 开发者,你可能会捡拾了一些提示和技巧一路走来你不知道!

入门

下载示例项目。你会看到,这是一个漏洞百出的程序!当你在 Xcode 中打开该项目时,它显示了至少八个编译器警告,始终是将来麻烦的预兆。顺便说一句,我们使用 Xcode 4.3 对于本教程,虽然 4.2 版应该工作一样。

注:要跟随本教程中,应用程序需要运行在 iOS 5 模拟器。如果您在您的设备上运行应用程序,你还是会崩溃,但他们可能不会发生相同的顺序。

在模拟器中运行应用程序,看看会发生什么。

The app crashes immediately.

嘿,它崩溃了!:-]

基本上有两种类型的可能发生的崩溃: SIGABRT (也称为 EXC_CRASH) 和EXC_BAD_ACCESS (其中 SIGBUS 或 SIGSEGV 的名义也可以显示)。

据崩溃去,SIGABRT 是一个非常好,因为它是受控制的崩溃。应用程序故意终止,因为系统识别应用程序做了它不能这样做。

另一方面,EXC_BAD_ACCESS,是很难调试,因为它只有当应用程序进入损坏的状态,通常是由于内存管理问题。

幸运的是,这第一次坠毁 (许多尚未来) 是 SIGABRT。SIGABRT 总是带有一条错误消息,您可以看到在 Xcode 的调试输出窗格 (底部窗口的右下角)。(如果看不到调试输出窗格中,点击你 Xcode 窗口以显示调试区域上右上角的视图图标部分的中间图标。如果仍然调试输出窗格不可见,您可能必须点击顶部的调试区域 — — 搜索字段旁边的图标的中间图标)。在这种情况下,它说,这样的事情:

Problems[14465:f803] -[UINavigationController setList:]: unrecognized selector sent toinstance 0x6a33840Problems[14465:f803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException',reason: '-[UINavigationController setList:]: unrecognized selector sent to instance 0x6a33840'*** First throw call stack:(0x13ba052 0x154bd0a 0x13bbced 0x1320f00 0x1320ce2 0x29ef 0xf9d6 0x108a6 0x1f7430x201f8 0x13aa9 0x12a4fa9 0x138e1c5 0x12f3022 0x12f190a 0x12f0db4 0x12f0ccb 0x102a70x11a9b 0x2792 0x2705)terminate called throwing an exception

很重要的是你学会破译这些错误消息,因为它们包含了什么问题的重要线索。在这里,有趣的部分是:

[UINavigationController setList:]: unrecognized selector sent to instance 0x6a33840

错误消息"无法识别的选择器发送以实例 XXX"意味着应用程序正试图调用不存在的方法。经常发生这种情况因为在错误的对象上调用的方法。在这里,所讨论的对象是 UINavigationController (位于 0x6a33840 的内存地址),方法是曲目:.

知道飞机坠毁的原因是好的但是你第一次的行动方针是找出代码中此错误发生。你需要找到的名称的源文件和行为不端的行的编号。你可以使用调用堆栈(也称为栈跟踪或回溯)。

当应用程序崩溃,Xcode 窗口切换到调试导航器的左窗格。报告显示在应用程序中,活跃的线程,并突出崩溃的线程。通常,将线程 1,主线程的应用程序,因为这是在哪里,你会做你的大部分工作。如果您的代码使用队列或后台线程,然后应用程序可能会崩溃中的其他线程。

The call stack. It doesn't show everything yet.

目前,Xcode 突出了在main.m main ()函数作为问题的根源。这不告诉你很多,所以你只好挖深一点。

若要查看更多的调用堆栈,一路向右拖动滑块底部的调试导航。在崩溃的时刻,将显示完整的调用堆栈:

The expanded call stack.

每个从该列表中的项目是一个函数或方法,从应用程序或从 iOS 框架之一。调用堆栈显示你什么函数或方法是在应用程序中当前处于活动状态。调试器已暂停应用程序和所有这些函数和方法现在被冻结了。

在底部, start (),函数在第一次调用。在某个地方在其执行它叫上面,main ()函数。这是该应用程序的起点,它总是会在底部附近。main ()又被称为UIApplicationMain()这是行的 (在 Xcode 的右窗格中突出显示的开头) 的绿色箭头指向在编辑器窗口中的行。

UIApplicationMain()进一步上涨堆栈,调用_run方法上的 UIApplication 对象,称为CFRunLoopRunInMode(),称为CFRunLoopRunSpecific(),依此类推,一直到__pthread_kill.

How a call stack works.

所有的这些函数和方法中调用堆栈,除了main (),呈灰色。这是因为他们来自内置 iOS 框架。有是没有源代码提供给他们。

此异常堆栈包含源代码唯一永恒的东西是main.m,所以这就是 Xcode 源代码编辑器中显示的内容,尽管它不是真的崩溃的真正来源。这往往混淆新的开发人员,但在一分钟内,我将显示你如何使它的意义。

为了好玩,点击任何一个其他的项从栈跟踪,你会看到一堆的汇编代码,可能你不有太大的意义:

If there is no source code, Xcode shows assembly.

哦,要是我们有源代码的!:-]

异常断点

使应用程序崩溃的代码中,你如何找到行?好吧,每当你得到这样的栈跟踪,异常被引发 (你可以告诉,因为函数调用堆栈中的一个叫objc_exception_rethrow的应用.)

当该程序抓住做不应该做的事情时,将发生异常。你现在看着是此异常的后果: 应用程序做了一些错事,异常,和 Xcode 显示你的结果。理想情况下,你会想要看到准确获取抛出该异常。

幸运的是,你可以告诉 Xcode 来暂停程序,只是那一刻,使用异常断点。断点是在某一特定时刻暂停您的程序的调试工具。你会看到更多的人在本教程中,第二部分,但现在您将使用具体的断点将暂停程序,只是之前获取引发异常。

若要设置异常断点,我们必须切换到断点导航:

The Breakpoint Navigator

底部是一个小小的 + 按钮。单击此按钮,然后选择添加异常断点:

Adding the Exception Breakpoint

新断点将添加到列表中:

After the Exception Breakpoint has been added

单击完成按钮关闭弹出框。Xcode 的工具栏中的断点按钮现在已启用的通知。如果你想要无任何已启用的断点运行应用程序,您可以简单地切换此按钮可关闭。但现在,离开它,再次运行该应用程序。

After the crash, the problematic source line is now highlighted.

这是更好!源代码编辑器现在指向一条线从源代码 — — 不再讨厌大会的东西 — — 和通知调用堆栈在左边 (您可能需要切换到调用堆栈通过取决于你怎么有 Xcode 设置中的调试导航) 也看起来不同。

显然,罪魁祸首就是这条线在 AppDelegate 的应用程序: didFinishLaunchingWithOptions:方法:

viewController.list = [NSArray arrayWithObjects:@"One", @"Two"];

再看看那条错误消息:

[UINavigationController setList:]: unrecognized selector sent to instance 0x6d4ed20

在代码中,"viewController.list = 东西"调用曲目:在幕后,因为"名单"是MainViewController类的属性。不过,根据错误消息 viewController 变量不指向到一个 MainViewController 对象,但对 UINavigationController — — 而且当然,UINavigationController 并没有"列表"属性!所以事情混淆了这里。

打开要看到窗口的 rootViewController 属性实际上指向的演示图板文件:

The storyboard has a navigation controller.

啊哈哈!演示图板的初始视图控制器是一个导航控制器。这解释了为什么 window.rootViewController 是一个 UINavigationController 对象,而不是你期望的 MainViewController。若要解决此问题,请替换应用程序: didFinishLaunchingWithOptions:以下列:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{UINavigationController *navController = (UINavigationController *)self.window.rootViewController;MainViewController *viewController = (MainViewController *)navController.topViewController;viewController.list = [NSArray arrayWithObjects:@"One", @"Two"];return YES;}

首先你从 self.window.rootViewController,对 UINavigationController 的引用,然后一旦你有了这些你也可以得到指针到 MainViewController 索要其 topViewController 导航控制器。现在 viewController 变量应该指向正确的对象。

注:每当你得到一个"无法识别选择器发送到实例 XXX"错误,请检查该对象是正确的类型和它实际上已具有该名称的方法。通常你会发现,你是对对象调用方法不同比你想象的因为一个指针变量可能不包含正确的值。

The different parts of the "Unrecognized selector" error message.

此错误的另一个常见原因是方法名称的拼写错误。你会看到一个这样的例子在位。

你第一次内存错误

那应该固定我们第一个问题。再次运行该应用程序。哎呦,它坠毁在同一行上,现在只有一个 EXC_BAD_ACCESS 错误。这意味着应用程序有一个内存管理问题。

Our first EXC_BAD_ACCESS error.

与内存相关的崩溃的来源很难精确定位,因为邪恶可能更早在程序中完成。如果一块故障代码的内存结构使人腐化,这结果可能不会显示,直到很久以后,和在一个完全不同的地方。

事实上,bug 可能永远不会出现在你在所有测试时,前后只有它丑陋的头在你的客户的设备上。你不想这样的事情发生!

然而,这个特定的崩溃,很容易解决。如果你看看源代码编辑器,Xcode 一直在警告你说这条线一直以来。在左边的行号上看到黄色的三角形吗?指示编译器警告。如果你点击黄色三角形,Xcode 应该弹出一个"修复它"建议像这样:

Xcode warns about a missing sentinel.

通过给它的对象,列表和此类列表 NSArray 对象应该终止使用零代码初始化,哨兵警告中提到。但这并不做和现在 NSArray 获取混为一谈。它试图读取,并不存在的对象和应用程序崩溃硬。

这是你真的不应该做,尤其是因为 Xcode 已经警告您关于它的错误。修复代码,如下所示添加到列表中的零 (或只是可以从菜单中选择"修复它"选项):

viewController.list = [NSArray arrayWithObjects:@"One", @"Two", nil];

"此类不是键值编码兼容"

运行该应用程序,再来看什么其他有趣的 bug 这个项目已经在商店为您。和你知道吗?它再次崩溃在main.m上。由于异常断点仍处于启用状态,我们看不到任何突出显示的应用程序源代码,这次失事真正没有发生任何应用程序源代码中。调用堆栈证实这: 这些方法都不属于该应用程序,除了main ():

The call stack for the "key value coding" crash.

如果你从楼顶下来看通过的方法名称,有 NSObject 和键-值编码发生了一些东西。下面是[UIRuntimeOutletConnection 连接]呼吁。我也不知道什么是,但它看起来像它已经跟连接插座。下面,是谈论从笔尖加载视图的方法。因此,那已经给你一些线索。

然而,没有方便错误信息是 Xcode 的调试窗格中。这是因为没有尚未引发该异常。异常断点已暂停程序,只是之前它会告诉你异常的原因。有时你得到部分的错误信息与异常断点启用,而有时你不知道。

若要查看完整的错误消息,请单击调试器工具栏中的"继续程序执行"按钮:

The Continue Program Execution button.

您可能需要单击它不止一次,但然后您会收到错误消息:

Problems[14961:f803] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<MainViewController 0x6b3f590> setValue:forUndefinedKey:]: this class is notkey value coding-compliant for the key button.'*** First throw call stack:(0x13ba052 0x154bd0a 0x13b9f11 0x9b1032 0x922f7b 0x922eeb 0x93dd60 0x23091a 0x13bbe1a 0x1325821 0x22f46e 0xd6e2c 0xd73a9 0xd75cb 0xd6c1c 0xfd56d 0xe7d47 0xfe441 0xfe45d 0xfe4f9 0x3ed65 0x3edac 0xfbe6 0x108a6 0x1f743 0x201f8 0x13aa9 0x12a4fa9 0x138e1c5 0x12f3022 0x12f190a 0x12f0db4 0x12f0ccb 0x102a7 0x11a9b 0x2872 0x27e5)terminate called throwing an exception

作为之前,您可以忽略底部的数字。他们代表调用堆栈,但你已经有了,以一种更方便 — — 和可读!— — 在左边的调试导航中的格式。

有趣的片段如下:

  • NSUnknownKeyException
  • MainViewController
  • "此类不是键值编码兼容为关键的按钮"

异常,NSUnknownKeyException 的名称通常是很好的指标是错误。它告诉你有什么地方是"未知的钥匙"。某天某地,显然是 MainViewController,和关键被命名为"按钮"。

当我们建立了所有这一切都发生加载笔尖时。应用程序使用演示图板,而不是笔尖,但内部演示图板只是一个集合家发钞银行,因此它必须是一个演示图板中的错误。

查阅 MainViewController 插座:

The button outlet in the storyboard.

在连接检查器中,您可以看到的视图控制器中心 UIButton 连接到 MainViewController 的"按钮"插座。因此,情节提要/笔尖指插座命名"按钮,"但根据错误消息,它找不到这个插座。

看一看MainViewController.h:

@interface MainViewController : UIViewController @property (nonatomic, retain) NSArray *list;@property (nonatomic, retain) IBOutlet UIButton *button; - (IBAction)buttonTapped:(id)sender; @end

@property 定义为"按钮"插座有,是什么问题吗?如果你一直关注编译器的警告,你可能会发现它已经。

如果没有,请查阅MainViewController.m/s @synthesize 列表。现在看到的问题吗?

代码不实际上多 @synthesize 按钮属性。它告诉 MainViewController,它有一个名为"按钮,"而不为它提供支持实例变量和 getter 和 setter 方法 (这是什么所 @synthesize 做)。

低于现有的 @synthesize 线,若要解决此问题在MainViewController.m中添加以下内容:

@synthesize button = _button;

现在应用程序应该不再崩溃当你运行它!

注:该错误通常"此类不是键值编码兼容键 XXX"时发生加载实际上并不存在的属性是指的笔尖。这通常发生在你删除插座属性,从您的代码,但不是能从笔尖的连接。

按下按钮

现在,该应用程序的工作 — — 或在至少启动没有问题 — — 同时,点击该按钮的时间。

The app finally starts up.

哇!应用程序崩溃与 SIGABRT 在main.m上。在调试窗格中的错误消息是:

Problems[6579:f803] -[MainViewController buttonTapped]: unrecognized selector sentto instance 0x6e44850

堆栈跟踪并不是太富有启发性。它列出一大堆的方法相关的一种方法或其他发送事件和执行操作,但是你已经知道了涉及行为。毕竟,你点击 UIButton 和,结果在被调用 IBAction 方法。

当然,你看到这个错误消息之前。被调用的方法不存在。这次的目标对象,MainViewController,看起来是那个合适的人,因为操作方法通常住在包含按钮的视图控制器。并且如果你看一下MainViewController.h,IBAction 方法是确实有:

- (IBAction)buttonTapped:(id)sender;

或者是它吗?错误消息表示方法名称是buttonTapped,但 MainViewController 有一种方法命名为buttonTapped:,以冒号结尾因为此方法接受一个参数 (名为"发件人")。方法名称的错误消息,从另外一方面,不包括冒号和因此不带任何参数。该方法的签名而是看起来像这样:

- (IBAction)buttonTapped;

在这里发生了什么?该方法最初没有参数 (某物所允许的操作方法),在这段时间的演示图板中建立了连接到这个按钮触摸内部事件。然而,一些时间后,方法签名更改为包含"发件人"参数,但未更新演示图板。

您可以在连接检查器按钮上的演示图板中看到:

The button's connections in the storyboard.

首先断开连接事件 (单击小 X) 触摸里面,然后再将其连接到主视图控制器但这次选择buttonTapped:方法。请注意,在检查器中连接有现在一个冒号后的方法名称。

运行应用程序,再点击按钮。什么?!你再"无法识别选择器"的消息,尽管这一次它正确地标识方法作为buttonTapped:,用冒号。

Problems[6675:f803] -[MainViewController buttonTapped:]: unrecognized selector sentto instance 0x6b6c7f0

如果你仔细看,编译器警告应指向你的解决方案再一次。Xcode 抱怨,MainViewController 的执行是不完整。具体来说,方法定义为buttonTapped:找不到。

Xcode shows an incomplete implementation warning.

来看看MainViewController.m的时间。当然似乎是buttonTapped:方法在那里,虽然......等等,它拼错了:

- (void)butonTapped:(id)sender

容易修复。重命名的方法:

- (void)buttonTapped:(id)sender

请注意,你不一定需要将它声明为 IBAction,虽然你可以这样做,如果你认为是整齐的。

注:这种事情是很容易赶上如果你注意到编译器警告。就个人而言,我将所有警告视为致命错误 (甚至还有选项为此生成设置屏幕中 Xcode) 和之前运行的应用,以及 Xcode 是很擅长指出了愚蠢的错误,像这些一样,它是明智的要注意这些提示,我会搞定的他们每一个人。

摆弄内存

你知道钻: 运行应用程序,点击按钮,等待飞机坠毁。没错,就是这样:

The app crashes on NSLog().

它是那些 EXC_BAD_ACCESS,另一个呀!幸运的是,在 Xcode 显示你到底在哪里崩溃发生了,在buttonTapped:方法:

NSLog("You tapped on: %s", sender);

有时这些错误可能需要一两个片刻登记在你的头脑,但再次 Xcode 借伸出援助之手 — — 只是水龙头黄色的三角形,看看有什么不对:

Objective-C strings must have a @ prefix.

NSLog()采用目的型 C 字符串,不是普通旧 C-字符串,所以插入 @ 将修复它:

NSLog(@"You tapped on: %s", sender);

你会注意到黄色警告三角不会消失。这是因为这条线有可能或不可能会崩溃,您的应用程序的另一个 bug。那些有趣的人。有时代码工作就好 — — 或至少出现上班就好 — — 而在其他时间它会崩溃。(当然,这些各种各样的崩溃仅发生在客户的设备上,从来没有对你自己)

让我们看看新的警告是:

Xcode warns about a format string issue.

%S 说明符为 C 样式字符串。C 字符串是只是一段记忆 — — 一个普通老的字节数组 — — 那终止由所谓的"NUL 字符,"真的只值 0。例如,C 字符串"崩溃!"看起来像这样在内存中:

What a C-string looks like in memory.

每当你使用一个函数或期望的 C 样式字符串的方法,你必须确保字符串的值为 0,以结束或功能将不能识别的字符串已经结束。

现在,当您指定 %s 以NSLog()格式字符串 — — 或在 NSString 的 stringWithFormat — — 该参数被解释好像它是一个 C 字符串。在这种情况下,"发件人"为参数,而是指向一个 UIButton 对象,这当然是 C 字符串的指针。如果无论"发件人"指向包含一个 0 字节,则NSLog()不会崩溃,但输出的东西如:

You tapped on: xËj

实际上,你可以看到这来自何处。再次运行该应用程序,点击按钮,等待飞机坠毁。现在,在左半部分的调试窗格中,右键单击"发件人"并选择"查看内存的 * 发件人"选项 (请务必选择一个带有星号在发件人)。

The view memory menu option.

Xcode 现在将显示你的内存内容在该地址,而它正是NSLog()打印出来。

Xcode shows the contents of memory.

然而,就不能保证是 NUL 字节,一个字节,你可能只是作为容易遇到 EXC_BAD_ACCESS 错误。如果你总是测试您的应用程序可能不会发生长时间在模拟器上,作为这种情况下总是可能对你有利你特定的测试环境中。这使得这些类型的 bug 很难跟踪。

当然,在这种情况下 Xcode 已经警告你关于错误的格式说明符,所以这个 bug 是很容易找到。但每当你使用 C 字符串或者直接操作内存,你必须非常小心,不糊弄别人的记忆。

如果你幸运的话,该应用程序将会总是崩溃和 bug 很容易找到,但更常见的是,应用程序将仅崩溃有时 — — 使这一问题很难再重现!— —,然后寻找 bug 可以采取上空前绝后。

修复NSLog()语句,如下所示:

NSLog(@"You tapped on: %@", sender);

运行应用程序并再一次摁下按钮。NSLog()做什么它应该,但看起来好像你不会崩溃在buttonTapped:尚未。

调试器和交朋友

这个最新的崩溃,Xcode 指向线:

[self performSegueWithIdentifier:@"ModalSegue" sender:sender];

在调试窗格中还有没有消息。像你以前,但也可以键入命令在调试器中得到错误消息,您可以按继续程序执行按钮。这样做的好处是应用程序在同一个地方可以呆在暂停。

如果您正在运行这从模拟器,你可以键入后 (lldb) 提示以下内容:

(lldb) po $eax

LLDB 是默认调试器 Xcode 4.3 和起来。如果你使用的旧版本的 Xcode,然后你有了 GDB 调试器。他们分享一些基本的命令,所以如果你 Xcode 提示说 (gdb) 而不是 (lldb),你应该仍然能够跟着没有问题。(顺便说一句,你可以切换中的计划编辑器中 Xcode 下运行操作, 调试器。你可以访问计划编辑器通过 Alt 点击运行在你 Xcode 窗口左上角的图标)。

蒲命令代表"打印对象"。$eax 符号指 CPU 寄存器之一。发生的异常,这个寄存器将包含指向 NSException 对象的指针。注: $eax 仅适用于模拟器,如果你需要使用的设备上调试注册 $r0。

例如,如果您键入:

(lldb) po [$eax class]

您将看到这样的事情:

(id) $2 = 0x01446e84 NSException

这些数字不重要,但很明显你在处理一个 NSException 对象在这里。

在此对象上,可以从 NSException 调用的任何方法。例如:

(lldb) po [$eax name]

这会给你这个案例的 NSInvalidArgumentException,异常的名称和:

(lldb) po [$eax reason]

这会给你的错误消息:

(unsigned int) $4 = 114784400 Receiver (<MainViewController: 0x6b60620>) has nosegue with identifier 'ModalSegue'

注:当你只是做"po $eax"时,它将调用该对象上的"描述"方法和打印它,在这种情况下也给你的错误消息。

这就解释了什么: 你正在尝试执行名为"ModalSegue"的界限,但显然 MainViewController 上有没有这种界限。

情节提要并显示的继续存在,但你忘记了要设置它的标识符,一个典型的错误:

Giving the segue an identifier.

变化的界限标识符为"ModalSegue"。再次运行该应用程序 — — 等待它 — — 点击按钮。哎哟,这不更崩溃的时间!但这里是挑逗的我们接下来的部分 — — 表视图,其中显示了本不应该是空!


跳转:应用程序崩溃定位查找 (二)

0 0
原创粉丝点击