[译]Xcode 8 中阶调试技巧

栏目: 编程工具 · IOS · 发布时间: 7年前

内容简介:[译]Xcode 8 中阶调试技巧

更新说明: 本教程由 George Andrews 升级为 Xcode 8 和 Swift 3。原文作者为 Brain Moakley。

软件开发中唯一不变的主题就是 bug。让我们面对现实吧,没有人能够一次就能做对。从输出错误到不正确的假设,软件开发就好比是蟑螂屋里烤蛋糕——只不过制造蟑螂的人就是开发者自己。

幸运的是,Xcode 提供了大量防止这种事情发生的工具。虽然你热衷于 debugger,但通过这些 工具 你能够做的更好,而不仅仅是查看变量和单步执行!

本文针对中级 iOS 开发者,你将学到一些少为人知但又非常重要的调试技巧,比如:

  • 去除 NSLog,而用断点日志代替
  • 通过编译脚本生成 TODO 和 FIXME 编译器警告
  • 通过表达式设置条件断点
  • 通过 LLDB 动态修改数据
  • 等等

我个人的目标是成为一个真正懒惰的开发者。我宁可麻烦在前,而享受在后。幸运的是,Xcode 为我节省下了“马丁尼时间”。它提供了许多工具,使我不再没日没夜地粘在我的电脑面前。

我们先来看看有哪些工具。拖过一把豆袋椅。打开饮料。让我们放松一下:]

本教程假设你熟悉 Xcode 调试器。如果你不知道如何在 Xcode 中进行调试,请先阅读 新手调试教程

开始

我为本教程准备了一个示例 app。你可以在 这里下载

这个 app 叫 Gift Lister,它会记录你想为谁购买的礼物。就像 Gifts 2 HD,它获得了 2012 年 印象最深刻的读者 App 。Gift Lister 和 Gift 2 HD 很像……当然要差许多。

首先,它有许多 bug。开发者(也就是我,只不过穿着不同的衣服)雄心勃勃,尝试用传统的方式修复这些 bug……但是无功而返:]

本教程会教你如何在尽可能偷懒的同时修复这个 app。

好了,让我们开始吧——但也不必太过紧张。:]

打开项目,看一眼它的文件。你会注意到这个 app 有一个简单的前端和一个简单的 Core Data 数据库。

注意:如果你不熟悉 Core Data,也没关系!Core Data 是一个面向对象的持久化框架,它有现成的 教程 。在本教程中,我们不会过多深入这个框架,也不需要和 Core Data 对象进行任何有意义的交互,因此你也不需要了解得太多。只需要知道 Core Data 会为你加载对象、保存对象就可以了。

大概浏览完之后,我们开始打开调试器。

打开 Debugger 控制台

进行任何调试过程之前,首先需要打开 debugger 控制台。点击主工具栏上的这颗按钮:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/2-show-debugger.png’ width=’350’/>

这颗按钮很好找,每次调试会话开始时点击这颗按钮,将避免你的指尖的不必要的磨损 ;] 为什么不让 Xcode 为你多做一些工作呢?

然后,打开 Xcode 偏好设置,通过 [command + ,] 或者 Xcode\Preferences 菜单。点击 Behaviors 按钮(齿轮图标)。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/3-update-behaviors-1-650x477.png’ width=’400’/>

点击左边的 Running\Starts。你会看到一堆选项。点击右边的第七个选项框,然后在最后边下拉列表中选择 Variables & Console View 。

在 Pauses 和 Generates Output 上重复同样动作,它们紧紧挨在 Starts 下边。

Variables & Console View 选项告诉调试器要显示本地变量清单,以及每当调试会话开始后显示控制台输出。如果你只想看到控制台输出,你可以选择 Console View。相反,如果只想看变量,则选择 Variable View。

Current Views 选项默认会显示最后一次调试的调试器视图。例如,如果你关闭了 Variables 仅仅显示控制台视图,那么下一次调试时就只会显示控制台视图。

关闭对话框,运行 app。

现在,每次编译运行 app 都会打开调试器——而不需要再点击那颗按钮了。尽管这个动作只需要 1 秒钟,但一周下来也会浪费你几分钟。毕竟,你的目标是做一个懒惰的程序员:]

应接不暇的 NSLog

在继续下一步之前,有一个重要的事情,就是回顾一下断点定义。

断点是程序中的一个时间点,允许你在运行程序的过程中执行某些动作。有时,程序可以在指定的某个点暂停,允许你查看程序状态或者单步执行代码。

你还可以运行代码,改变变量,让计算机引述莎士比亚语录。我们将在后面的内容中进行所有的这些动作。

注意:本教程将设计部分断点的高级用法。如果你还对诸如步进、步出、跳过之类的概念不太清楚,请阅读 My App Crashed, Now What? 教程

运行 app。然后,加一个新朋友,以便记录他的礼物。不出意外,当你添加新朋友时,app 崩溃了。我们来搞定它。

这是你第一次运行 app 的样子:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/4-first-stack-trace-1.png’ width= ‘500’/>

这个项目需要头脑清醒。现在,你无法看到编译错误的原因。要找到它,需要添加异常断点,以记录错误的原因。

切换到断点导航器:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/5-breakpoint-nav-2.png’ width=’340’/>

然后,点击面板底部的 + 号按钮。在弹出菜单中,选择 Exception Breakpoint… 。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/6-exception-breakpoint-add-2.png’ width=’240’/>

你会看到这个对话框:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/34-exception-breakpoint-edit-1.png’ width=’300’/>

Exception 字段允许你通过 O-C、C++ 或所有语言来触发断点。保持默认的 All 不改变。

Break 字段允许你在错误被抛出还是被捕捉时暂停。保持默认的 on throw(抛出时)不变。如果你是在自己的代码中进行了错误处理,则可以选择 On Catch(捕捉时)。对于本教程,请使用 on throw。

后面两个字段稍后介绍。点击对话框外任意地方,关闭对话框,然后运行 app。

这次调试器给出了更清晰的结果:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/Screen-Shot-2017-02-24-at-1.43.32-PM.png’ width= ‘600’/>

看一眼控制台——它打印了一些消息,其中大部分都是不需要的。

调试代码时日志是至关重要的。日志信息需要被过滤,否则控制台将被垃圾信息所占据。干扰信息会浪费你的时间,因此必须过滤掉它们,否则你会在一个问题上花去更多的时间。

打开 AppDelegate.swift,你会看到在 didFinishLaunchingWithOptions 方法中看到一大堆过时的消息。选中它们,然后删除。

然后搜索其他的日志输出语句。打开搜索栏,查找 in viewDidLoad。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/9-search-dialog.png’ width=’200’/>

点击搜索结果,这将打开 FriendSelectionViewController.swift 并跳到产生了日志的那句代码。

注意,这次使用的是 print 语句,而不是 NSLog 语句。通常在 Swift 中,标准输出使用 print,当然你也可以用 NSLog。

以 log 方式输出日志信息有一个重要的地方,当你在多线程中输出日志时,你不必自己保持同步。这两种方法都可以用在调试会话中,将信息输出到控制台。

这里,管理你的日志语句的工作开始逐步累积。它看起来不太多,但每分钟都会增加。到了项目后期,这种零零散散的时间加起来很容易就突破了几个小时。

硬编码日志语句带来的另一个“好处”是,每当你添加了一个语句到代码库中,就相当于添加了新的 bug 到代码中。只需要敲了几个键,再加上自动完成,以及稍微不注意——你以前正常的 app 就多了一个 bug。

是时候将这些日志语句移除代码中了,它们只属于断点。

首先,注释两条 print 语句。然后,在每条语句左边的边栏中左键,添加一个断点。

你的代码窗口看起来应该是这个样子:

[译]Xcode 8 中阶调试技巧 https://koenig-media.rayw menderlich.com/uploads/2017/01/10-logging-breakpoints.png’ width = ‘300’/>

ctrl+左键或者右键点击第一个断点,选择 Edit Breakpoint。在对话框中,点 Add Action,从 Action 下拉列表中选择 Log Message 。在文本字段,输入 in viewDidLoad。对话框最终看起来应该是这个样子:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/11-viewDidLoad-breakpoint-expanded.png’ width=’500’/>

关闭对话框,运行 app。现在你将看到控制台中输出了 in viewDidLoad 字样,但这次我们使用的是断点,而不是 NSLog 语句。

注意:在本教程中,我们将在每次修改断点之后点击 build & run,以便更容易解说。关键是要记住:断点是运行时添加的。你可以在程序执行过程中添加任意多的断点。包括 NSLog 语句。

考虑一个重要的问题:程序会在断点位置停下,但我们想让它继续怎么办?其实很简单。

ctrl+左键(或右键)点击断点,选择 Edit Breakpoint。在对话框底部,勾选 Automatically continue after evaluating 选框。

现在运行 app。这次它输出了日志……但它在第二个断点处停止。

ctrl+左键(或右键)点击第二个断点。点击 Add Action,选择 Action 列表中的 Log Message,输入 Loading friends…。勾选对话框底部的 Automatically continue after evaluating 选框。

再次运行 app。app 正常运行……等你添加朋友时它才盘亏。你手足无措 :]

无论如何,你还有更多的工作要做。ctrl+左键(或右键)点击第一个断点,将 in viewDidLoad 替换成 %B。再次运行程序。控制台将输出如下内容:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/12-console-log.png’ width=’200’/>

%B 输出方法名。还可以用 %H 输出方法被调用的次数。此外还可以使用简单表达式。

因此我们可以yoga:%B has been touched %H times。控制台将显示:viewWillLoad() has been touched 1 times.

运行 app,添加一个朋友,程序崩溃。如果你想触发前面添加的异常断点,你可以点击 continue,这样你会看到崩溃日志。调用堆栈会打印如下内容:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Friend''

Core Data 代码有问题。

浏览代码,你会看到实体对象是通过 persistentContainer 的 viewContext 来创建的。你的直觉会告诉你,可能是 persistenContainer 导致了问题。

仔细看控制台,你会找到这个调用栈:

Failed to load model named GiftList
CoreData: error:  Failed to load model named GiftList
Could not fetch. Error Domain=Foundation._GenericObjCError Code=0 "(null)", [:]

这个信息告诉你 CoreData 无法加载一个数据模型,叫做: GiftList。如果你查看这个项目的数据模型,你会发现它实际上应该叫做 “GiftLister”。

看一眼 AppDelegate.swift 中的其它代码。

因为我的疏忽大意,我将 persistentContainer 的 name 参数写错了。我将 “GiftLister” 给写成了 “GiftList”。

将 “GiftList” 改成 “GiftLister”。

let container = NSPersistentContainer(name: "GiftLister")

运行 app。现在添加一个朋友。哈—— app 现在好像正常了。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/13-gift-lister-working.png’ width=’120’/>

断点和表达式

看起来不错,但你可能注意到一点,断点输出的消息中不包含时间,对于调试来说这可是很有用的哦!幸好,通过断点表达式这个问题很好搞定!

注意:日期输出十分有用,但同时会让日志输出变慢,因为系统需要查询日期信息。记住哪怕是调用日志输出自身都会导致 app 性能降低。

让我们恢复原来的日志语句。右键(或 ctrl+左键)点击 FriendSelectionViewController.swift 中的第二个断点。点击 Edit Breakpoint。在 action 列表中将 Log Message 修改为 Debugger Command ,然后在文本框中输入:

expression NSLog("Loading friends...")

类似这个样子:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/14-expression-nslog.png’ width=’600’/>

Debugger 命令将在运行时计算这个表达式。

运行 app。你会看到:

2012-12-20 08:57:39.942 GiftLister[1984:11603] Loading friends...

在断点中添加 NSLog 语句意味着你不需要为输出某些重要的数据而停止程序的执行,这会减少你产生新 bug 的机会,因为你根本没有碰代码——最好的一点是,你不需要在发布前争分夺秒地删除代码中的 debug 语句。

现在关闭 app 中的日志输出。非常简单,点击 debugger 视图中的断点按钮即可。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/15-disable-breakpoints-button.png’ width=’300’/>

点击这个按钮,运行 app。现在控制台干净多了。你也可以在断点导航器中关闭某个断点。

现在,不得不一一注释代码中日志语句的日子一去不复返了!:]

MARKSs、TODOs、FIXMEs、我的个天!

接下来要做的事情是创建更多的朋友,这样你可以记录一张建议给他们的礼物清单。

运行 app。点击 Add a friend 行。app 会显示另一个 view controller,包含一个姓名输入框和一个日期选择器。输入名字,选择生日,然后点击 OK 按钮。

这将会回到根控制器,而你添加的朋友将显示在列表中。再次点击 Add a friend。

输入新朋友的名字,这次将生日设置为 2010 年 2 月 31 日。

在正常的日期选择器中,这样一个日期根本是不可能出现的。然而对于我们的这个神奇则不然。因为大脑抽风,我决定使用普通的 picker 来代替 date picker。这样我就不得不重写日期校验逻辑,同时也带来了几个 bug。

点击 OK 按钮。很不幸,这个无效的日期被保存了。让我们来看看这会导致什么样的错误吧。

打开 AddFriendViewController.swift ,在 saveFriend 方法开始处添加一个断点。

注意:在一个大文件中定位方法比较费事。比较麻烦的做法是逐行扫描代码,直到找到这个方法。另外一种方法是使用跳转栏,通过方法列表来查找。我最喜欢的方法是使用查找,当然不是在搜索栏中,而是在跳转栏中搜索。点击跳转栏,然后开始键入文本。你的方法名就会出现,就像你在搜索栏中所做的一样。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/16-searching-in-jump-bar.png’ width=’300’/>

在模拟器中,点击 Add a friend 按钮,很之前一样,添加一个无效的生日。单步执行,知道你到达这一行:

if name.hasText, isValidDateComposedOf(month: selectedMonth, day: selectedDay, year: selectedYear) {

步进到 isValidDateComposedOf 方法。很显然,校验代码出错了——这里什么都没有!只有一个注释,表明以后会实现它。

注释是一种很好的描述代码块意图的方法,但你无法用它们来进行任务的管理。再小的项目,也有太多的任务,注释的任务经常会被遗忘。

真正防止它们被遗忘的方法是让它们非常显眼。其中一种方式就是让信息在跳转栏中显眼。

打开跳转栏,你会看到:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/17-todo-comment.png’ width=’300’/>

你还可以用 FIXME: 或者 MARK:。

代码中的 MARK:、TODO: 和 FIXME: 注释语句会显示在跳转栏中。此外,如果你在 MARK: 后面加一个连字符,比如 MARK: - UIPickerViewDataSource,跳转栏会在注释前面添加一个水平分割线,就更容易阅读了!

这些语句并不会让编译器当成警告或错误,但会比方法底部的普通注释更容易看到。这是让注释以及注释所标注的任务形成一个待办任务清单,从而突出于代码库。

但是,为什么不能让 Xcode 编译器为代码中出现的 TODO: 、 FIXME: 注释发出警告呢?我真心觉得这很不错!

要做到这一点,你需要在项目中添加一个 build 脚本,搜索代码中所有的 TODO: 和 FIXME: 注释,并将之作为编译器警告。

要创建 build 脚本,请在项目导航器中选中项目,然后点 Build Phases。点击 + 按钮添加一个新的 Run Script Phase。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/31-add-run-script-phase-650x200.png’ width=’500’/>

然后,编写 build 脚本如下:

TAGS="TODO:|FIXME:"
echo "searching ${SRCROOT} for ${TAGS}"
find "${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"

你的 Run Script 代码看起来是这样的:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/32-run-script-code.png’ width=’550’/>

编译项目,打开 issue 导航器:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/33-shell-script-invocation-warnings-1.png’ width= ‘300’/>

现在 TODO: 注释被显示成一个 shell 脚本调用警告,这样你总没办法遗忘了吧?:]

变量视图和返回值

现在,我们来看一个从 Xcode4.4 就有的小功能。

重新运行 app,保持在空的校验方法中的断点不变。现在,步出这段代码。打开 debugger 的 Variables 视图,你会看到:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/18-variables-view-return-value.png’ width=’600’/>

显示返回值并不是多稀罕的功能,但它足以节省你很多时间。试想从这里调用这段代码:

if name.hasText, isValidDateComposedOf(month: selectedMonth, day: selectedDay, year: selectedYear) {

这句代码会调用 isValidDateComposedOf 方法并立即在表达式中使用它的返回值。

在这个功能出现之前,你需要离开这行代码,如果你想查看返回值的话,还必须打印它。现在,你可以简单地步出一个方法,然后在调试器中查看返回值。

成功的 debugging 的条件

有时,我们不得不以固定的间隔修改应用程序的状态。有时这种改变发生在大量时间序列之中,这使得正常的 debugging 变得十分困难。这就要使用到条件(conditions)了。

现在,app 中列出了几个好友,点击他们的名字将打开礼物界面。这是一个简单的分组表格,可以对表格进行排序,以决定某件礼物是否应该购买。

点击导航条上的 add 按钮添加一件礼物。名称输入 shoes,价格输入 88。然后点 OK 按钮。这样 shoes 就显示在礼物清单中了。

接着添加这些礼物:

  • Candles / 1.99
  • Sleigh / 540.00
  • XBox / 299.99
  • iPad / 499.99

哎呀,你突然想到,其实你想添加的应该是 PS4 而不是 XBox。你可以点击这一行进行修改,但为了方便我们演示的缘故,你可以通过 debugger 来进行修改。

打开 GiftListsViewController.swift 找到 cellForRowAtIndexPath。添加一个断点在这一行下面:.

if (gift) {

看起来是这个样子:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/19-gift-breakpoint.png’ width=’450’/>

在这个断点上右键(或者 ctrl+左键),选择 Edit Breakpoint。

然后来添加条件。你可以把它当成一个简单的 if 语句。添加这个代码:

gift.name == "Xbox"

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/29-condition-breakpoint.png’ width=’500’/>

然后,点击 segmented 控件上的 Bought 按钮。表格会刷新,但断点不会被触发。

点击 segmented 控件的 Saved 按钮。这次会暂停了,同时所选中的礼物会高亮显示在控制台中。

在控制台中,输入下列代码:

expression gift.name = "PS4"

现在,点击 Run 按钮,表格会继续加载。PS4 会替换掉礼物中的 XBox。

你可以通过修改循环变量来达到同样效果。ctrl+左键(或右键)点击这个断点,选择 Edit Breakpoint。这次,将 Condition 栏清空,将 Ignore 设置为数字 2,然后点 Done。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/30-ignore-2-times-breakpoint.png’ width=’400’/>

现在,点击 segmeted 控件的 Bought 按钮,然后点击 segmented 控件的 Saved 按钮。这将触发同一个断点。

要确认这是我们需要的对象,可以输入:

(lldb) po gift

现在,和之前一样修改对象的状态:

(lldb) expression gift.name = "Xbox"

表格会显示出修改的结果。实时修改是不是更爽?

执行清理动作

在开发数据驱动的 app 时,经常需要清除数据库。可以用很多办法去做这个事情,比如重置 iPhone 模拟器,或者在 Mac 上查找真实的数据库并进行删除。重复干这样的事让人厌倦,我们可以偷点懒,让 Xcode 为我们做这个。

一开始需要创建一个 shell 脚本。一个 shell 脚本是一个命令集合,让操作系统自动执行某些动作。要创建 shell 脚本,需要用 application 菜单创建一个新文件。点击 File\New\File 或 Command-N。在分类中,先选择 Other 然后选择 Shell Script 类型。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/22-shell-script-dialog.png’ width=’500’/>

文件名设为 wipe-db.sh。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/23-shell-script-name.png’ width=’500’/>

为了真正清除数据库,我们需要用 remove 命令以及数据库的完整路径(包括当前用户名)。你可以用 Finder 或者终端程序找到数据库所在位置,然后复制/粘贴它的路径到 shell 脚本中。但在 Xcode 8 中,保存数据库的文件夹在每次编译、运行 app 时总是不固定的。

要解决这个问题,我们可以使用 whoami 命令输出当前用户,用通配符 * 来代替会变的文件夹。

因此可以这样编写脚本:

rm /Users/$(whoami)/Library/Developer/CoreSimulator/Devices/*/data/Containers/Data/Application/*/Library/Application\ Support/GiftLister.sqlite

保存并关闭脚本。

默认情况下,shell 脚本是只读的。你可以在终端中将脚本设置为可执行。

如果你找不到终端程序,你可以在应用程序文件夹的工具中找到它。

打开终端,进入你的 home 目录,输入:

YourComputer$ cd ~

然后列出目录中的文件:

YourComputer$ ls

我们需要切到项目目录。如果你的项目目录位于桌面文件夹,你可以这样进入到项目目录:

YourComputer$ cd Desktop
YourComputer$ cd GiftLister

如果要向上返回一级目录,你可以用:

YourComputer$ cd ..

经过在终端中进行一番艰苦的摸索,我们终于可以看到我们的项目文件了。要将 shell 脚本修改为可执行,需要:

YourComputer$ chmod a+x wipe-db.sh

chmod 用于修改文件的权限。a+x 表示文件可以被所有用户、组和其它人执行。

哇……好麻烦。深呼吸。这是必需的。做这么多的工作目的就是为了偷懒。:]

关闭终端,回到 Xcode。打开 AppDelegate.swift。

在 didFinishLaunchingWithOptions 第一行打断点。右键(或 ctrl+左键)点击断点,选择 Edit Breakpoint,添加一个 action 并选择 Shell Command。在后面的对话框中,点击 Choose 然后选择我们所创建的脚本。勾选 Automatically continue after evaluating 选项,然后关闭对话框。

如果模拟器正在运行,请关闭它。

编译运行,数据库被删除了。

模拟器默认会缓存许多数据,因此最好是用 Xcode 的 Product/Clean 菜单执行一次“干净”的 build,然后再 build & run。否则,当你启动 app,停止、再次运行后。缓存的数据会和全新的数据库混在一起。

当 App 还在开发时,清空数据库只需要按一下按钮。如果不需要这个功能,只需要关闭这个断点。

注意:我们创建的脚本仅仅包含了一条简单的 Unix 命令,用于删除文件。你也可以在脚本中通过一个 PHP 文件来干同样的事情。你还可以打开一个 Java 程序、 Python 脚本或者任意电脑上的其他程序。这样,你不需要学习 shell 脚本就能通过断点来操作底层操作系统。

加分章节:深入保存方法

此时,我们的 app 已经拥有不少数据了。是时候保存它们了。

对于这类 app,保存工作应当经常性的进行,以免丢失数据。

我们的 app 的情况有所不同,它只会在用户退出 app 时保存数据。

如果你现在没有看见根 View Controller,请点击导航条上的 Back 按钮返回根控制器。然后按下 Home 键。你可以点击模拟器菜单的 Hardware\Home 或者 Shift-Command-H 返回模拟器桌面。

从 Xcode 中终止程序,然后编译运行。table view 中是空的。这个 app 什么也没有保存。

打开 AppDelegate.swift。在 applicationDidEnterBackground 方法,你会看到问题在于 doLotsOfWork 方法。这个方法无法按时完成,iOS 就终止了 app,无论它有没有完成收尾的工作。这导致 saveData 方法还没有被调用。

首先需要确保数据被保存。将 saveContext 方法调用放到 doLotsOfWork 方法调用之前:

saveContext()
doLotsOfWork()

现在在 doLotsOfWork 这行打断点。右键(或 ctrl+左键)点击断点,然后选择 Edit Breakpoint。在 Action 中选择 sound action 以及 Submarine 声效。在使用 sound action 时,尽量避免使用系统声音,以免断点被忽略。

然后,勾选 Automatically continue after evaluating。

最后,点击 build & run。

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/25-sound-breakpoint.png’ width=’400’/>

当 app 再次启动,添加一个新的好友,然后在模拟器中按下 Home 键。当 app 关闭后,你会听到有海底泉的声音,表明我们的数据已经保存。

用 Xcode 停止程序,按下 Run。你会看到所有的数据都显示了。通过声音,我们能够知道某些代码已经被执行,不需要我们查看日志。你还可以提供自定义生效,比如在某种严重崩溃时播放爆炸声音。

这需要将你的声音文件放到这个文件夹:

YOUR_HOME_DIRECTORY/Library/Sounds

在使用这些声音之前你必须重启 Xcode,但注意这可能成为某些人的恶作剧:]

还有一个有趣的地方。找到 FriendSelectionViewController 中的第一个断点,右键(或 ctrl+左键)点击这个断点,选择 Edit Breakpoint,在对话框中,点击 + 按钮,这里可以添加更多的动作,而不仅仅是断点本身。

选择 Log Message 动作,这次,我们输入 To be, or not to be。勾选 Speak Message 单选框,然后点击 Done。对话框最终显示成这个样子:

[译]Xcode 8 中阶调试技巧 https://koenig-media.raywenderlich.com/uploads/2017/01/26-shakespeare-dialog.png’ width=’450’/>

build & run,好好乐一下子吧!

注意:除了新奇,这个功能也是很有用的!语音消息在调试复杂的网络代码等情形时尤其有用。

结束

你可以在 这里 下载已完成的项目。

如你所见,Xcode 调试工具在面对日常开发中的挑战时,非常灵活。例如,LLDB 提供了动态查看和修改代码的能力,而不会增加更多的 bug。

无论如何,这只是一个开始。LLDB 还提供了许多其它特性,比如自定义变量概览、动态断点、用 Python 定义调试脚本等。

当然,将 NSLog() 和调试代码删除会是一个麻烦,但最终你会发现你的项目变得更健壮。你不需要在发布的前一晚为移除所有的调试代码而忧心忡忡,也不需要为创建清晰的调试环境而编写复杂的宏代码。Xcode 为你提供了能够让你轻松度过最后一天的一整套工具。

如果你想了解更多,可以从 LLDB 开始。一个学习 LLDB 的好去处就是 Brian Moakley 录制的视频教程: Using LLDB in iOS

在 WWDC 2016 大会中也介绍了LLDB 的新特性: Debugging Tips and Tricks

有任何问题和建议,请在下面留言!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

马云内部讲话

马云内部讲话

阿里巴巴集团 / 红旗出版社 / 2010-12 / 28.00元

马云的话有什么其妙的地方? 为什么员工会把自己的CEO当作偶像? 世界都处在迷茫期,他如何确立阿里巴巴的价值观? 他怎样给已经是富翁的员工寻找新的激情? 风暴袭来,他怎么克服内心的恐惧? 他在互联网合纵连横的动机何在?一起来看看 《马云内部讲话》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具