现在的位置: 首页 > 综合 > 正文

大牛的XCODE调试技巧

2013年12月06日 ⁄ 综合 ⁄ 共 4679字 ⁄ 字号 评论关闭

有这样一种情形:当我们正在快乐的致力于我们的app时,并且什么看都是无比顺利,但是突然,坑爹啊,它崩溃了。(悲伤地音乐响起)

当程序崩溃的时候怎么办 part-1

我们需要做的第一件事就是:不要惊慌。

修复崩溃不是很困难的。假如你崩溃了,并且胡乱的改些东西,而且还在不停的念着咒语希望bug神奇的自动消失,你大多数情况下都会使情况更麻烦。相反的,你需要知道一些系统的方法,并且学习怎么找到崩溃和他的原因。

第一件需要知道的就是在你的代码中准确的找到crash发生的地方:在那个文件,那一行。Xcode debugger将会帮助你,但是你需要懂得怎么样最好的使用它,这也是这篇教程展示给你的。

这篇教程对于所有的开发者都是有利的。即使你是一个很有经验的ios开发者,你也可能会从中学习到一些你不知道的小窍门。

准备开始

下载这个例子程序。你将会看到这是一个有bug的程序。当你打开这个项目的时候,xcode会显示至少8个编译警告,这个通常都是危险的信号。顺便说一下,我们使用xcode4.3来做这篇教程,4.2的版本也应该没有什么问题。

注意:为了跟随这篇教程,这个编译生成的app需要运行在ios5的模拟器上面。假如你运行这个app到你的设备上,你也会崩溃,但是他们可能不会发生和教程一样的情况。

在模拟器上面运行你的app,你将会看到发生了什么。

当程序崩溃的时候怎么办 part-1

嘿,他崩溃了。

有两种最基本的crash类型常发生:SIGABRT(也叫EXC_CRASH)和EXC_BAD_ACCESS(也可能会是SIGBUS或者SIGSEGV)。

就crash而言,SIGABRT是一个比较好解决的,因为他是一个可掌控的crash。App会在一个目的地终止,因为系统意识到app做了一些他不能支持的事情。

EXC_BAD_ACCESS是一个比较难处理的crash了,当一个app进入一种毁坏的状态,通常是由于内存管理问题而引起的时,就会出现出现这样的crash。

幸运的是,第一种崩溃(也是大多数崩溃)是SIGABRT,SIGABRT通常会在xcode的Debug Output窗口(在窗口的右下角)输出一些错误的信息。假如你没有看到Debug Output窗口,在你的xcode窗口的右上角一组图标中点击中间那个,假如还是没有看到Debug Output窗口,你需要点击这个小窗口的右上角的中间那个图标,他靠近搜索框。在这个情况下,会展示一些下面东西:

  1. Problems[14465:f803] -[UINavigationController setList:]: unrecognized selector sent to
  2. instance 0x6a33840
  3. Problems[14465:f803] *** Terminating app due to uncaught exception'NSInvalidArgumentException',
  4. reason: '-[UINavigationController setList:]: unrecognized selector sent to instance 0x6a33840'
  5. *** First throw call stack:
  6. (0x13ba052 0x154bd0a 0x13bbced 0x1320f00 0x1320ce2 0x29ef 0xf9d60x108a6 0x1f743
  7. 0x201f8 0x13aa9 0x12a4fa9 0x138e1c5 0x12f3022 0x12f190a 0x12f0db40x12f0ccb 0x102a7
  8. 0x11a9b 0x2792 0x2705)
  9. terminate called throwing an exception

了解这些错误消息是非常重要的,因为他们包含了错误在那里的重要线索,一下就是需要关注的部分:

  1. [UINavigationController setList:]: unrecognized selector sent to instance0x6a33840

“unrecognized selector sent to instance XXX” 这条错误消息意味着你的app正在试着执行一个不存在的方法。这种情况的发生,主要是都是一个方法被错误的对象调用了(也就是这个对象没有这个方法,但是你调用了他,就错了)。例如在这里这个问题上,对象就是UINavigationController (在内存地址0x6a33840上),方法就是setList:。

知道crash的原因是很好的,但是你的第一行动目的就是指出这个错误的发生在代码的那个地方。你需要找到源文件的名字和这个错误方法在那一行。你通过使用call stack(就像堆栈跟踪(stacktrace)或者回溯(backtrace))就可以知道这些东西。

当你的程序crash了时,在xcode窗口的左边小窗口会启动Debug Navigator(调试导航)。他会展示在这个app中那个线程是活动的,并且高亮显示crash了的线程。通常他会是线程1,这个app的主线程,这个线程也是你会做最多工作的线程。假如你的代码里面使用了队列(queues)或者后台线程(background threads),这个app也可能会在其他的线程里面崩溃。

当程序崩溃的时候怎么办 part-1

 

当前xocde就高亮显示了main.m里面的main()函数。但是那些东西并没有告诉你很多,所以你需要继续的向深层次的挖掘。

为了看到堆栈的更多信息,拖拽Debug Navigator底部的滑块到最右边。它将会展示出崩溃时全部的堆栈信息:

当程序崩溃的时候怎么办 part-1

这个列表里面的每一项都是一个来这个app或者ios的framework里的方法或者函数。堆栈展示了当前活跃在这个app里面的方法或者方法。调试器(debugger)已经暂停了这个程序,并且所有的这些方法和函数在这个时候也被冻结了。

在底部的函数start(),第一个被调用。在他的执行里面的有些地方,,main()函数在他之前。(Somewhere in its execution it called the function above it, main().)。他是应用程序的开始入口点,并且它经常在底部附近。Main()也叫UIApplicationMain()(这个针对的是ios哈,并不是其他所有程序都是这样的)。在这个编辑窗口里面用绿色箭头指示的那一行(就是在这个教程最开始前面程序崩溃时停止在那个图片上,高亮显示的部分)。

进一步来看看这个堆栈,UIApplication()在UIApplication对象里调用_run方法,_run方法里面又调用CFRunLoopRunInMode()方法,CFRunLoopRunInMode()方法里面又调用CFRunLoopSpecific()方法,就这样一直向下调用,一直到__pthread_kill。

当程序崩溃的时候怎么办 part-1

所有在这个堆栈里面的函数和方法都是灰色的,除了main()函数。那是因为他们都来自内置的ios frameworks(ios内置框架)。所以没有针对他们可见的源码。

在这个堆栈里面唯一的东西就是你有main.m的源码,因此xcode的代码编辑器就显示了它,即使他不是这个崩溃的真正原因。但是这个经常混淆初学者,但是马上我将展示怎么样来弄懂它。

开个玩笑,点击这个堆栈里面的任意一项,你将会看到许多的汇编代码,这些你可能完全不理解:

当程序崩溃的时候怎么办 part-1

加入我们得到那样的源码,我想很多人都会说:坑爹啊。

异常断点

你怎么样找到是代码里面的哪一行使app崩溃的?无论什么时候,你得到的一个想这样的堆栈路径,一个异常通过这个app抛出。(你多半会说因为堆栈里面有一个函数叫objc_exception_rethrow。)

当程序由于做了一些他不能完成的事情时,一个异常就会发生。你所看到的就是这个异常的结果:app做了一些错的事情,异常被抛出,xcode展示异常的结果。理想情况下,你想要的准确的看到异常在那里抛出的。

幸运的是,通过使用Exception Breakpoint(异常断点),你可以告诉xcode在一个特定的时候暂停这个程序。断点是一个在特定时刻暂停你的程序的调试工具。你将会第二篇教程里面看到更多关于他们的信息,但是现在你将会使用一个特殊的断点,它将会在抛出异常前暂停你的程序。

为了设置异常断点,我们不得不切换到Breakpoint Navigator(断点导航器):

当程序崩溃的时候怎么办 part-1

在底部有一个小的加号(“+”)按钮。点击它,并且选择Add Exception Breakpoint:

当程序崩溃的时候怎么办 part-1

一个新的断点将会被增加到这个列表里:

当程序崩溃的时候怎么办 part-1

点击Done按钮使弹出的窗口消失。注意在xcode工具栏上面Breakpoints button(断点按钮)是有效的。加入你不想要带着任何断点运行你的app,你可以简单的开关这个按钮到off。但是现在,让它打开,并且再一次运行这个app。

当程序崩溃的时候怎么办 part-1

太好了!代码编辑器现在停止并且指到了代码中的其中一行,不再在令人烦躁的汇编代码了,并且注意在在左边的的Debug Navigatot(调试导航器)里面显示的堆栈信息也不一样了。

显然的,问题就出在AppDelegate里面的application:didFinishLaunchingWithOptions:方法里:

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

仔细再次看看这个错误消息:

  1. [UINavigationController setList:]: unrecognized selector sent to instance0x6d4ed20

在这个代码里面,“viewController.list = something”这种方式隐式的调用了setList:方法,也就是set方法,因为“list”是MainViewController类的一个属性。然而,通过这个错误消息,我们知道viewController这个变量没有指向MainViewController对象,而是指向了UINavigationController,所以显然的,UINavigationController没有“list”属性!所以这些变量在这里混淆了。

打开Storyboard文件,看看window的rootViewController属性实际上是指向那个的:

当程序崩溃的时候怎么办 part-1

哈哈!Storyboard的最初的view controller是一个Navigation controller。这就是为什么window.rootViewController是一个UINavigationController对象,而不是你自认为的MainViewController。为了修改这里,使用下面的代码来替代application:didFinishLaunchingWithOptions:里面的:

  1. - (BOOL)application :( UIApplication *)application didFinishLaunchingWithOptions :( NSDictionary *)launchOptions
  2. {
  3. UINavigationController *navController = (UINavigationController*)self.window.rootViewController;
  4. MainViewController *viewController = (MainViewController*)navController.topViewController;
【上篇】
【下篇】

抱歉!评论已关闭.