我的应用程序崩溃,现在怎么办? - 第1部分
要做的第一件事是:不要惊慌!
固定崩溃并不需要是硬的。你可能恶化的情况下,如果你吓坏了,并开始改变的东西是随机的,希望这个bug会奇迹般地消失,只要你说出正确的咒语。相反,你需要采取有条不紊的方法,并学习如何崩溃的方式,通过讲道理。
业务的第一顺序是要找出确切位置在你的代码崩溃发生在哪个文件哪一行。Xcode调试器将帮助你,但你需要了解如何善用它,而这正是本教程将向你展示!
本教程是为所有开发人员,从初级到高级。即使你是一个经验丰富的iOS开发者,你可能会拿起一些提示和技巧,一路上你不知道!
入门
下载的示例项目。正如你所看到的,这是一个错误的程序!当你在Xcode中打开该项目,显示至少有八个编译器警告,这始终是一个提前风吹草动。顺便说一下,我们使用Xcode 4.3对于本教程,虽然4.2版本应该工作一样好。
注:要跟随本教程中,应用程序需要运行iOS 5的模拟器上。如果您的设备上运行的应用程序,你还是会崩溃,但他们可能不会发生在同一顺序。
在模拟器中运行的应用程序,看看会发生什么。
嘿,它崩溃了!: - ]
基本上有两种类型:SIGABRT(也称为EXC_CRASH),EXC_BAD_ACCESS(SIGBUS或SIGSEGV的名字下也可以显示),可能发生的崩溃。
至于崩溃,SIGABRT是一个相当不错的,因为它是一个可控制的崩溃。终止确认系统的应用程序的目的,因为该应用程序做了一件不应该的。
EXC_BAD_ACCESS,另一方面,调试了很多困难,因为它只发生时,应用程序得到一个损坏的状态,通常是由于内存管理的问题。
幸运的是,这第一次崩溃(许多人还没来)是一个SIGABRT。SIGABRT信号总是在Xcode的调试输出窗格(右下角的窗口),你可以看到一个错误消息。(如果您没有看到调试输出窗格中,点击中间的图标在视图图标部分Xcode窗口右上角的显示调试区。如果调试输出窗格仍然是不可见的,你可能会必须挖掘在调试区的顶部 - 中间的图标旁边的图标,在搜索字段)。在这种情况下,它说这样的事情:
问题14465:F803 ] - [ UINavigationController的SETLIST:]:无法识别的选择发送到 实例0x6a33840的 问题14465:F803 ] ***因未捕获异常NSInvalidArgumentException“终止应用程序, 原因是:' - UINavigationController的SETLIST:]:无法识别的选择发送实例0x6a33840' ***首先抛出调用堆栈: ( 0x13ba052 0x154bd0a 0x13bbced 0x1320f00 0x1320ce2 0x29ef 0xf9d6 0x108a6 0x1f743 0x201f8 0x13aa9 0x12a4fa9 0x138e1c5 0x12f3022 0x12f190a 0x12f0db4 0x12f0ccb 0x102a7 0x2792 0x2705 0x11a9b ) 终止称为抛出异常 |
这一点很重要,你要学会破译这些错误消息,因为它们包含了重要的线索是怎么回事错误。在这里,最有趣的部分是:
[ UINavigationController的SETLIST:]:无法识别的选择发送到实例0x6a33840 |
错误消息“无法识别的选择发送到实例XXX”是指该应用程序试图调用不存在的方法。通常发生这种情况,因为该方法被称为错了对象。在这里,问题的对象是UINavigationController的(位于在内存地址0x6a33840),和方法是SETLIST: 。
知道飞机坠毁的原因是良好的,但你的第一个行动是要弄清楚其中在代码中发生此错误。你需要找到源文件的名称和行为不端的行数。为此,您可以使用的调用堆栈(也被称为堆栈跟踪或回溯)。
当一个应用程序崩溃时,Xcode窗口的左窗格中切换到Debug导航。它显示了的活动线程应用程序中的,并突出崩溃的线程。通常将主题1,主线程的应用程序,因为这是在那里你会做你的大部分工作。如果您的代码使用队列或后台线程,然后应用程序可以在其他线程崩溃。
目前,Xcode的main.m强调的main()函数,问题的根源。这并不是告诉你非常多,所以你就必须挖得更深一些。
要看到更多的调用堆栈,拖动滑块在调试导航器的底部一路的权利。这将显示完整的调用堆栈崩溃的时刻:
从这个名单中的每一个项目是一个函数或方法从应用程序或从一个iOS的框架。调用堆栈显示你什么函数或方法是目前活跃在app。调试器已暂停的应用程序,现在,所有这些功能和方法都冻结的时间。
在底部启动的功能() ,被称为第一。某处在其执行,它在它上面调用函数main()中。这是应用程序的起点,它将永远是底部附近的主要()又称为UIApplicationMain() 。这是该行的绿色箭头(在Xcode在右窗格中突出显示的行开始)是指向在编辑器窗口。
更进一步堆栈,UIApplicationMain()被称为_run UIApplication对象,其中名为CFRunLoopRunInMode的() ,它被称为CFRunLoopRunSpecific()方法,等等,所有的方式__pthread_kill。
除了main()中,所有这些函数和方法的调用栈,是灰色的。这是因为他们来自内置的iOS框架。有没有为他们提供的源代码。
main.m唯一,你必须在这个堆栈跟踪源代码,所以这是什么Xcode的源代码编辑器中显示,尽管这不是真的崩溃的真正来源。这往往混淆了新的开发者,但在一分钟内,我会告诉你如何使感。
为了好玩,点击任何一个堆栈跟踪的其他项目,可能没有多大意义,你,你会看到一堆汇编代码:
哦,如果只有我们的源代码的!: - ]
异常断点
那么,你是怎么找到的应用程序崩溃的代码行吗?嗯,这样每当你得到一个堆栈跟踪,异常被抛出的应用程序。(你可以告诉,因为调用堆栈中的函数之一名为objc_exception_rethrow的。)
程序发生异常时被捉住做的事情,它不应该这样做。你现在看到的是这个异常的后果:应用程序做错了什么,已经抛出异常,,Xcode中显示的结果。理想情况下,你要清楚地看到,该异常被抛出。
幸运的是,你可以告诉Xcode中暂停程序在刚才那一瞬间,使用例外断点。à 断点是在一个特定的时刻,暂停你的程序调试工具。在本教程的第二部分,你会看到更多的人,但现在你会使用特定的断点暂停程序异常被抛出前。
设置异常断点,我们必须切换到断点导航:
在底部是一个小的+按钮。单击此按钮并选择“添加例外”断点:
一个新的断点将被添加到列表:
单击“完成”按钮关闭弹出。请注意,Xcode的工具栏的“断点”按钮现在已经启用。如果你想运行的程序没有任何启用了断点,你可以简单地切换这个按钮来关闭。但现在,离开它,并再次运行应用程序。
这是更好!源代码编辑器现在指向一条从源代码 - 没有更讨厌组装的东西 - 和通知,在左边的调用堆栈(你可能需要通过调试导航切换调用堆栈取决于你有怎样的Xcode集上)看起来也不同。
显然,罪魁祸首就是这条线在AppDelegate中的应用:didFinishLaunchingWithOptions:方法:
|
以该错误消息再次一起来看看:
[ UINavigationController的SETLIST:]:无法识别的选择发送到实例0x6d4ed20 |
在代码中,“viewController.list =东西”调用SETLIST的幕后,因为“名单”是一个属性上MainViewController类。然而,根据该错误消息的viewController变量不指向一个MainViewController对象,但一个UINavigationController - UINavigationController的,当然,没有一个“清单”属性!这样的事情在这里混了。
打开的Storyboard档案RootViewController的窗口的属性,看看有什么实际指向:
啊哈!其实,故事情节的初始视图控制器是一个导航控制器。这解释,为什么window.rootViewController是一个UINavigationController的对象,而不是你所期望的MainViewController。为了解决这个问题,更换应用:didFinishLaunchingWithOptions:为以下内容:
|
首先,你得到一个参考UINavigationController的从self.window.rootViewController,和一旦你有,你可以得到的指针MainViewController问其topViewController导航控制器。现在的viewController变量应指向正确的对象。
注意:每当你得到一个“无法识别的选择发送到实例XXX”的错误提示,检查的对象是正确的类型,它实际上有一种方法使用该名称。通常情况下,你会发现,你调用一个方法,对不同的对象,比你想的,因为一个指针变量可能不包含正确的价值。
此错误的原因是另一种常见的方法名称的拼写错误。你会看到一个这样的例子,在一个位。
您的第一个内存错误
这应该有固定我们的第一个问题。再次运行该应用程序。哎呀,它在同一行崩溃,现在只用一个EXC_BAD_ACCESS错误。这意味着应用程序有一个内存管理问题。
源记忆体相关的崩溃往往是很难确定的,因为邪恶可能已经在程序中要早得多。如果发生故障的一段代码破坏内存结构,这个结果可能不会出现,直到很久以后,在一个完全不同的地方。
事实上,错误可能永远不会为你显示出来,而在所有的测试中,客户对你的设备上,只张牙舞爪。你不希望这样的事情发生!
然而,这种特殊的崩溃是很容易修复。如果你看看源代码编辑器,Xcode中已经警告你关于这条线一直。看到在左边行号旁边的黄色三角形?这指示编译器警告。如果你点击黄色三角形,Xcode中应该弹出一个“修复”这样的建议:
代码初始化一个NSArray对象给它的对象的列表,这些列表都应该是零,终止使用,警告中提到的哨兵。但是,却没有这样做,现在的NSArray迷糊。它试图读取对象不存在,很难应用程序崩溃。
这是一个错误,你真的不应该做的,特别是因为Xcode中它已经向您发出警告。修复代码加入零名单如下(或者,你可以简单地选择“修复”选项的菜单):
|
“这个类是不是键值编码兼容”
再次运行应用程序,看到这个项目有什么其他有趣的错误在你的商店。你怎么知道?它再次崩溃main.m。由于异常断点仍处于启用状态,我们看不出有什么突出的应用程序源代码,这时候真正的崩溃并没有发生任何的应用程序的源代码。调用堆栈证实这一点:这些方法都属于应用程序,主(除外):
如果你看看通过方法名从上面下去,有一些东西是对用NSObject的和键-值编码去。下面是一个调用到[UIRuntimeOutletConnection连接]。我不知道那是什么,但它看起来像它的东西做连接网点。那下面的方法谈论从笔尖加载意见。所以给你一些线索。
然而,有没有方便的错误消息,Xcode的调试窗格。这是因为还没有被抛出的异常。异常断点处之前已暂停程序,它会告诉你异常原因。有时你会得到一个局部的错误消息启用异常断点,有时你不这样做。
要看到完整的错误信息,点击“继续执行程序”按钮,在“调试”工具栏:
您可能需要点击它不止一次,但随后你会得到错误消息:
问题14961:F803 ] ***终止应用程序由于到未捕获异常NSUnknownKeyException“, , 原因:'[<MainViewController 0x6b3f590>的setValue:forUndefinedKey:]:这个类是不 键值编码兼容的关键按钮。 ***首先抛出调用堆栈: ( 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 ) 终止称为抛出异常 |
像以前一样,你可以忽略的数字在底部。它们代表了调用栈,但你已经拥有了一个更方便 - 可读! - 调试在左侧导航格式。
有趣的位是:
- NSUnknownKeyException
- MainViewController
- “这个类是没有关键值编码标准的关键按钮”
名称的的例外,NSUnknownKeyException,往往是一个很好的指标,什么是错的。它会告诉你,有一个“未知的关键”的地方。这地方是显然MainViewController的,关键是名为“按钮”。
正如我们已经建立了,这一切都发生了,而加载笔尖。应用程序使用一个故事情节,而不是笔尖,但内部的故事情节只是一个集合的笔尖,所以它必须是一个错误的故事情节。
离开网点MainViewController:
在连接督察,你可以看到,在中心的视图控制器连接到MainViewController的“按钮”出口的UIButton。情节提要/笔尖指出口命名为“按钮”,但根据该错误消息无法找到这个插座。
看看在MainViewController.h:
|
@属性定义“按钮”出口是存在的,所以有什么问题吗?如果你一直在关注的编译器的警告,你可能已经想通出来了。
如果没有,检查MainViewController.m / s的合成列表。你看现在的问题吗?
的代码实际上并不@合成按钮属性。它告诉MainViewController的,它有一个命名为“按钮,”不提供后盾实例变量和getter和setter方法(是@合成做什么)的财产。
添加以下MainViewController.m低于现有的@合成线来解决这个问题:
|
现在的应用程序不再崩溃,当你运行它!
注:错误“这一类不是密钥值编码兼容的关键XXX”通常发生当加载一个笔尖,指的是一个实际上并不存在的属性。这通常发生在当你删除从您的代码,但不能从笔尖连接插座物业。
按下按钮
现在,应用程序的工作原理 - 或者至少是启动时没有问题 - 点击该按钮,它的时间。
哇!该应用程序崩溃main.m SIGABRT信号。调试窗格中的错误消息:
[ 6579:F803 ] - [ MainViewController buttonTapped的问题:无法识别的选择发送 实例0x6e44850, |
是不是太照明的堆栈跟踪。这都与一个或其他方式发送事件和执行操作的方法,列出了一大堆,但你已经知道参与行动。毕竟,你窃听一个UIButton的,结果,在一个IBAction方法被称为。
当然,你见过这个错误讯息。被调用的方法,不存在。这一次的目标对象,MainViewController,看起来是正确的,因为行动方法通常生活在视图控制器包含按钮。如果你看一下在MainViewController.h,IBAction方法是确实存在的:
|
或者是什么?错误消息说方法名是buttonTapped的的,但有一个方法MainViewController名为buttonTapped:在最后一个冒号,因为这个方法接受一个参数(名为“发件人”)。另一方面,从错误消息的方法名,不包括一个冒号,因此,不带任何参数。该方法的签名看起来是这样的,而不是:
|
这里发生了什么事?该方法最初没有东西是允许的动作方法有一个参数,此时情节板中的连接按钮的触摸事件里面。然而,后一段时间内,该方法的签名被改变,包括“发送者”参数,但没有被更新的故事板。
你可以看到这个情节板中的连接按钮督察:
首先断开触摸里面的事件(点击小X),然后将其连接到主视图控制器再次,但这次选择的buttonTapped:方法。请注意,在连接督察现在有一个冒号后的方法名称。
运行的应用程序,并再次点击按钮。什么?你再次得到“无法识别的选择”消息,尽管这一次,它正确地识别方法buttonTapped:结肠。
问题[ 6675:F803 ] - [ MainViewController buttonTapped:]:无法识别的选择发送 实例0x6b6c7f0, |
如果你仔细观察,编译器的警告应该指向你的解决方案。Xcode的抱怨,实施MainViewController是不完整的。具体来说,该方法定义为buttonTapped:是找不到的。
时间看MainViewController.m。当然是一个buttonTapped:方法在那里,虽然...等一下,它的拼写错误:
|
很容易解决。重命名的方法:
|
请注意,你不一定需要将其声明为IBAction,尽管你可以这样做,如果你认为是整洁的。
注:这种事情是很容易赶上,如果你注重编译器警告。个人而言,我对待所有警告都视为致命错误(甚至有一个选项,这在Xcode构建设置屏幕),我会解决他们的每一个运行的应用程序之前。Xcode中指出这些愚蠢的错误,比如,是相当不错的,要注意这些提示,这是明智的。
梅辛与记忆
你知道演练:运行的应用程序,点击该按钮,等待崩溃。没错,那就是:
这是另一个那些EXC_BAD_ACCESS的,亚克西!幸运的是,Xcode中显示事故发生的确切位置,在buttonTapped:方法:
|
有时,这些错误可能需要一两个时刻或注册在你的心中,但Xcode中再次出手相助 - 只需轻按黄色三角形来看看什么是错的:
NSLog的()需要一个Objective-C型字符串,而不是一个普通的老C字符串,所以插入@将修复它:
|
您会注意到,黄色三角形警告不走。这是因为此行还有一个bug可能会或可能不会崩溃,您的应用程序。这些都是有趣的。有时代码工作就好了-或者至少是似乎工作得很好-而在其他时候,它会崩溃。(当然,这些各种各样的崩溃发生在客户的设备,从来没有在自己的)。
让我们来看看新的警告是:
%s指定用于C风格字符串。A C字符串就是一段记忆 - 一个普通的老的字节数组 - 终止所谓的“NUL字符,”这是真的只值0。例如,C字符串“哗啦!”看起来是这样的记忆:
每当你使用一个函数或方法,预计C风格字符串,你必须确保字符串结束的数值为0,或功能将无法识别字符串已经结束。
现在,当你指定的%s NSLog的()格式字符串-或者在NSString的stringWithFormat -然后参数的解释,如果它是一个C字符串。在这种情况下,“发件人”的参数,它是一个一个UIButton的对象,这是绝对不是一个C字符串的指针。如果任何“发件人”点包含一个0字节,然后NSLog的()将不会崩溃,但输出的东西,如:
你拍了拍:xËj |
实际上,你可以看到来自哪里。再次运行的应用程序,点击按钮,等待崩溃。现在,在调试窗格中的左半部分,右键单击“发件人”并选择“查看内存*发件人”选项(请一定要选择一个与前面的发件人中的星号)。
Xcode将显示你的记忆内容在该地址,它正是NSLog的()打印出来。
然而,有没有保证有一个NUL字节,你可以很容易碰到EXC_BAD_ACCESS错误。如果你一直在测试您的应用程序在模拟器上可能不会发生很长一段时间,因为情况总是在特定的测试环境对你有利。这使得这些类型的bug非常难以跟踪。
当然,在这种情况下的Xcode已经警告过你在错误的格式说明符,所以这个特定的错误很容易被找到。但只要您是使用C字符串或直接操纵内存,你必须非常小心,不要乱周围别人的内存。
如果你幸运的话,应用程序将总是崩溃和错误很容易找到,但更常见的是,应用程序将只是有时会崩溃 - 使问题难以重现! - 然后追捕的bug可能需要上百年难遇的。
修复NSLog的()语句如下:
|
运行的应用程序,并按下按钮即可。,NSLog的()做什么是应该的,但看起来好像你还没有完成崩溃buttonTapped:尚未。
交朋友与调试器
对于这个最新的崩溃时,Xcode点行:
|
在调试窗格中有没有消息。您可以按继续执行程序“按钮像以前那样,但你也可以在调试器中键入一个命令,得到的错误信息。这样做的好处是可以保持暂停该应用程序在同一个地方。
如果你运行这个模拟器,你可以键入以下后(LLDB)提示:
( LLDB )$ eax中宝 |
LLDB为默认调试器的Xcode 4.3和。如果你使用的是旧版本的Xcode,那么你有GDB调试器。他们分享一些基本的命令,所以,如果你的Xcode提示说(GDB),而不是(LLDB),你仍然应该能够跟随没有问题。(顺便说一下,你可以调试器之间的切换计划编辑在Xcode下的运行动作。您可以访问计划按ALT攻运行在您的Xcode窗口左上角的图标编辑器。)
宝命令代表“打印对象”符号$ eax中是指一个CPU寄存器。在一个异常的情况下,这个寄存器将包含一个指针的NSException的对象。注:$ eax中仅适用于模拟器,如果你在设备上进行调试,你需要使用寄存器R0。
例如,如果你输入:
( LLDB ) PO [ $的EAX类] |
你会看到这样的事情:
(ID ) $ 2 = 0x01446e84 NSException的 |
这些数字并不重要,但在这里你正在处理一个NSException的对象,这是显而易见的。
您可以对这个对象调用任何方法从NSException的。例如:
( LLDB ) PO [ $的EAX名称] |
这会给你的名字异常,在这种情况NSInvalidArgumentException,并且:
( LLDB ) PO [ $的EAX原因] |
这会给你的错误消息:
(无符号整数) $ 4 = 114784400的接收机(< MainViewController:0x6b60620 > )没有 “赛格瑞与标识符ModalSegue的' |
注:当你只是做“宝$ eax中”,它会调用对象上的“说明”的方法和打印,在这种情况下,还为您提供了错误消息。
所以,解释这是怎么回事:你试图执行一个SEGUE名为“ModalSegue”的,但显然没有这样的SEGUE在MainViewController。
其实,故事情节也表明一个SEGUE是存在,但你忘了设置标识,典型的错误:
更改SEGUE的标识符“ModalSegue。”再次运行程序, - 等待 - 轻按按钮。哇,这个时候没有更多的崩溃!但这里有一个传情我们的下一个部分 - 表视图,显示了不应该是空的!
从这里去哪里?
那么,什么该空表吗?我要抱着你现在悬念。你会解决它在本教程的第二部分,还有一些更有趣的错误,你很可能会遇到你的编码生活。此外,在第二部分中,您将添加一些更多的工具,调试武库,包括NSLog的()语句,断点和僵尸对象。
当一切都说过和做过的,我保证,该应用程序将运行应该!更重要的是,你已累计在自己的应用程序,当您遇到这些挫折的技能 - 因为你不可避免地会。