[译]Xcode 8 中阶调试技巧

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

内容简介:[译]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 。

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


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

查看所有标签

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

高性能网站建设进阶指南

高性能网站建设进阶指南

Steve Souders / 口碑网前端团队 / 电子工业出版社 / 2010年4月 / 49.80元

性能是任何一个网站成功的关键,然而,如今日益丰富的内容和大量使用Ajax的Web应用程序已迫使浏览器达到其处理能力的极限。Steve Souders是Google Web性能布道者和前Yahoo!首席性能工程师,他在本书中提供了宝贵的技术来帮助你优化网站性能。 Souders的上一本畅销书《高性能网站建设指南》(High Performance Web Sites)震惊了Web开发界,它揭示......一起来看看 《高性能网站建设进阶指南》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具