当程序崩溃的时候怎么办 Part-2

泰然教程组出品,转载请保留出处并通知泰然!翻译:大侠自来也;校对:Iven

原文地址:http://www.raywenderlich.com/10209/my-app-crashed-now-what-part-2

欢迎回到当程序崩溃的时候怎么办 教程!

在这个教程的第一部分,我们介绍了SIGABRT和EXC_BAD_ACCESS错误,并且举例说明了一些使用xcode调试器(Xcode debugger)和异常断点(Exception Breakpoints)解决问题的策略。

但是我们的app仍然有一些问题!就像我们看到的,他工作的并不是很好,并且这里仍然有许多潜在的可能崩溃的问题。

幸运的是,在这个教程的第二部分,也是最后一部分,我们可以学习更多的技术来处理这些问题。

所以我们就不在啰嗦了,让我们回到继续修正这个充满bug的app中吧!

Getting Started: When What’s Supposed to Happen, Doesn’t

 

在第一部分我们停止的地方,经过许多的调试工作之后,我们运行这个程序他是不会崩溃的。但是他却展现了一个没有预料到的空的table,就像下面一样:

当你觉得一些事情应该发生,但是却没有发生的时候,这里有些你可以使用一些技巧来排除问题。在这个教程里面,我们首先是学习使用NSlog来解决这个问题。

这个table view controller的类是ListViewController。在一系列的任务执行之后,这个app应该装载ListViewController,并且在屏幕上面显示出来。你可以做一个测试,来确定view controller的方法是执行了的。所以viewDidLoad这个方法看起来应该是一个好地方来做测试。

在ListViewController.m,增加一个NSLog()到viewDidload,就像下面一样:

当你运行这个app时,你应该期望当我们点击了“Tap Me”按钮后在调试窗口看到“viewDidLoad is called”这样文字。现在就来试试,点都不惊讶,在调试窗口什么也没有出现。那就意味着ListViewController类根本没有被使用!

这个多半意味着,你可能忘记了告诉storyboard你想要为table view controller场景使用ListViewController类。

由上图我们可以看出,在身份检查器(Identity Inspector)的类属性区域是设置的默认值UITableViewController。改变这个Custom Class下面的class为ListViewController,然后再一次运行这个app。现在在调试窗口应该就会出现“viewDidLoad is called”文字:

但是这次app将会再一次崩溃,但是却是一个新的问题。

注意:一旦你的代码好像没起什么什么作用的话,放置一些NSLog()在确切的地方,来看看是否这个方法是被执行了的和cpu通过怎么样路径执行这个方法。使用NSLog()来测试你假设将会执行的代码。

Assertion Failures

这个新的有趣的崩溃。它是一个SIGABRT,并且在调试窗口打印出来的是以下消息:

我们得到的是一个执行UITableView的一些方法的一个“断言错误(assertion failure)”。当某些东西出错了之后,一个断言是一个内部相容性的检查器,并且会抛出一个异常。你也可以放置断言在你的代码里。例如:

在上面的方法里面,我们让一个NSString对象作为这个函数的变量,但是代码却不允许调用者传递一个nil或者长度小于3的字符串。假如这些条件中的一个不匹配的话,这个app将会终止,并且抛出一个异常。

你可以使用断言来作为一个防御性编程技术,因此你应该确定这个就是我们想要的代码行为。断言通常只在调试编译下有用的,因此他们对发布到app store的最终的app是没有运行时的影响的。

在这个情况下,某些情况触发了一个UITableView的断言错误,但是你并没有完全确定在那个地方。App也是停止在main.m里面,并且在执行堆栈里面只包含了框架(framework)的方法。

从这些方法的名字,我们可以猜测这个错误发生在重画这个tableview的某些地方。例如,我们可以看到layoutSubviews和_updateVisibleCellsNow:这些名字的方法。

继续运行这个app来看看是否可以得到一些比较好的错误消息—–记住,现在只是在抛出异常的时候暂停了程序,并没有崩溃。点击继续程序按钮,或者在调试窗口键入下面的命令:

你可能不得不多点击几次继续按钮,“c”命令也是一个简短的继续指令,和点击继续按钮一个效果,并不是就直接执行到最后。

现在这个调试窗口喷发出一些比较有用的信息:

太好了,这是一个相当好的一个线索。显然这个UITableView的数据源没有从tableView:cellForRowAtIndexPath:方法返回一个有效的cell,因此在ListViewController.m方法里面增加一些调试输出信息来看看:

你增加一个NSLog()标记。再一次运行这个app,看看输出了什么:

从以上信息我们可以看出,调用dequeueReusableCellwithIdentifier:返回的却是nil,这就意味着使用“Cell”作为标识符的cell可能不存在(因为这个app使用的是标准的cell的storyboard)。

当然,这也是愚蠢的bug,并且毫无疑问的是,在以前解决这个需要很长的时间,但是现在却不是了,因为xcode已经通过静态编译警告了你:“Prototype cells must have reuse identities。(标准的cell必须有重用的标识)”。这个是不能忽视的警告:

打开storyboard,选择这个标准的cell(在tableview的顶端,并且显示的是“Title”的单独的一个cell),并且设置cell的标识符为“Cell”:

将那个修复了之后,所以的编译警告应该没有了。运行这个app,现在这个调试窗口应该会打印出来:

Verify Your Assumptions

你的NSLog()打印出来的消息,已经告诉我们6个table view cell被创建了,但是在table上面什么都看不见。怎么回事呢?假如你在模拟器里面到处点击一下,你将会注意到tableview中6个cell中的第一个却能够被选中。所以,显然cells都是存在的,只是他们都是空的:

是时候需要更多的调试记录了。将先前的NSLog()标记改变一下:

现在你打印出来就是你的数据模块的内容。运行这个app,看看显示出来的是什么:

上面的很好的解释了为什么在cell里面什么都没有看到的原因:因为这个文字(text)始终是nil。然而,假如你检查你的代码,并且在initWithStyle:方法里面显示的添加了很多的字符串到list array里面:

就像上面那样,这是测试你的假设是不是正确的一个很好的方法。可能你还想更准确的看看这个array里面到底有什么东西。改变先前在tableView:cellForRowAtIndexPath:里面的NSLog()为这样:

至少这样可以给你展示一些东西。运行这个app。假如你还没准备好猜测会发生什么情况,调试窗口已经给你打印出来了:

你可能感兴趣的:(exception,list,xcode,null,Access,工具)