这个画面是不是很熟悉?
两种常见crash:SIGABRT以及EXC_BAD_ACCESS
SIGABRT是可控的crash,因为系统知道应用出现了一些不该出现的错误比如
这些信息中有一些给出了错误的线索,比如
“unrecognized selector sent to instance XXX” 这样的错误意味着,程序调用了不存在的方法,即某个对象没有该方法,而你却用该方法向其发送消息。
EXC_BAD_ACCESS,调试起来比较困难,由于内存管理问题,app出现了一些错误的状态。
首先得知道问题出在哪,为了解决这个问题,你可以使用调用栈(也叫栈轨迹或者回溯)。当程序崩掉时,会出现如下图,Xcode(会自动切换到Debug导航栏)
如图,它会显示所有处于活动状态的线程,在崩掉的线程处会有高亮显示,如上图的14 main。通常情况下它会在线程1(Thread1 程序的主线程)崩掉,因为大部分工作都是在主线程上完成的,当然,如果你用到队列或者后台线程,App同样会在其他线程崩掉。
如上图,高亮显示在main.m里面的main函数,但是main函数没有提供有用的信息,所以我们需要进一步挖掘出有用的信息。你可以滑到Debug导航栏的底部,将底部的滑杆推到最右边。它会显示崩溃时的调用栈。如下图所示
调用栈将会显示当前处于活动状态的函数或者方法,由于崩溃,调试器(debugger)暂停了程序,相应得所有的方法和函数也就被冻结了。底部的start方法(main下面)首先被调用,它会调用main函数,即一个应用的入口函数,main函数通常位于调用栈的底部。Main函数会调用UIApplicationMain方法,也就是在main函数中被高亮显示的那一行即
继续来看调用栈,UIApplicationMain调用_run,紧接着_run调用CFRunLoopRunSpecific(),如此按照调用栈顺序调用,直到进行到__pthread_kill函数结束。
以上调用栈中的方法(或函数)除了main以外,都是ios框架内置的,是看不到源码的。
所以你点击其中的任何方法,出现的都是一些栈踪迹,而非源代码。
看到这些栈踪迹,是不是感觉很困惑?
下面我们来解读一下栈踪迹,选择任何一个main以外的函数
设置exception breakpoint的方法如下
在这个窗口底部有个“+”点击之后,会自动添加一个exception breakpoint。
现在再run,现在它会在异常抛出处停下来,并在这一行高亮显示
根据console我们可以知道,我们向viewcontroller发送了一个并不属于他的方法,这时我们就可以检查出自己的错误所在,然后进一步完善自己的程序。
小窍门:当你看到“unrecognizedselector sent to instance XXX”这种错误时,通常是给某个对象实例发送了一个本不属于它的方法的消息
可能的原因:1.方法名拼写错误
2.该方法并不属于该对象
关于内存管理的crash,很难精确找出错误所在,因为它可能很早就已经发生了,发生内存泄露时,他不会马上把这个错误暴露出来,这是和SIGABRT最大区别所在。
发生这种错误可能的原因:
1. 初始化数组或者字典时没有在末尾处添加nil,导致编译器不知道你的目的。
错误:NSArray *array = [NSArray arrayWithObjects:@“1”,@“2”];
正确:NSArray *array = [NSArray arrayWithObjects:@“1”,@“2”,nil];
2. 本来已经释放的对象,却再向它发送消息,如:
[a release]; //假设此时retaincount为0
[a someMethod];//此时就会crash
修改后,再run,会发现程序还是crash掉了(当然不是因为前两个错误),最终停止在main函数里,这时肯定有疑惑,第一步不是已经设置好了exception breakpoint了吗,为什么没有在异常抛出点停下来而在main里面停下来呢?Bingo,下面继续。
如果你从上往下浏览调用栈,你会发现有一些是和NSObject and Key-Value Coding相关的。然后就是UIRuntimeOutletConnectionconnect,看起来像是outlet连接有错,然后下面是加载xib时出现的问题,这样我们就得到了一些有用的线索。在console里面没有出现有用的信息,这时我们有两种方法来解决
1.如下图
(也可以在console中输入c命令 c=continue效果是一样的)
然后在console里面会出现相应的错误信息
图中下半部分的16进制数可以忽略,那是一些调用栈的相关信息,注意上面两行,可以知道抛出的异常为NSUnknownKeyException问题发生在MainViewController这个类中的setValue:forUndefinedKey方法,因为Mainviewcontroller这个类是没有button这个属性(property)。所以在.h中添加一个名为”button”的输出口
小窍门:“this class is not key valuecoding-compliant for the key XXX”类型的错误通常发生在加载xib文件时,由于不存在相关的属性名。所以在代码中移除输出口属性时,同样在xib文件中移除相应的连接。
2.
在console中输入如下命令:po $eax 这个好处时,程序还是停留在那里没有像方法1那样被继续执行。 $eax 代表Cpu的寄存器,这个寄存器有一个指向NSException对象的指针。$eax只适用于模拟器调试,当真机调试时,可以使用$r0
输入该命令后,回车,将会有相关的异常描述(description)
断言是内部兼容的检测异常抛出的工具。
断言错误:
我们可以在自己的代码中添加断言来检测异常。如:
上面方法不允许传入的string变量为空或者长度小于3,只要任意一个条件不满足,则程序异常终止并会抛出异常。断言的好处是,他只会出现在调试模式下,对最终发布时的应用没有影响。
从调用栈中我们可以看出,问题出现在table view的重绘。这时我们可以用内存管理错误中给出的两种方法来进行调试。
当程序crash并抛出EXC_BAD_ACCESS错误时,我们可以使用下面的方法来找出错误所在。
点击Stop右边会弹出如下对话框
再进行如下设置
在把程序run一遍,在console(调试窗口)得到如下crash信息
勾选上Enable Zombie Objects,当你release某个对象时(若释放之后retaincount为0),但是为它分配的内存没有被deallocate(释放),相反,它会被标记为”undead”。如果之后你想再调用这块内存,程序会识别出这个错误然后终止并抛出“message sent to deallocated instance”这么一个错误。根据相应的错误信息,可以猜测出自己程序中大概那些地方会存在一些隐藏的bug。
综合上面所说,可以总结出一些调试方法
本文Demo
参考资料:Ray Wenderlich
尊重他人劳动成果,转载请注明出处。谢谢!