前言

Xcode 内置了许多工具可以协助开发者进行高效快速的 Debug,例如 LLDBInstrumentsDebug View HierarchyDebug Memory Graph 等。本文将介绍 LLDB 中有用的指令,以及怎么使用 Instruments 解决内存相关的问题。

LLDB

LLDBLLVM 中的调试器组件,支撑调试 CObjective-CC++ 编写的程序,Swift 社区保护了一个版别,增加了对该语言的支撑,LLDBXcode 的默许调试器。关于娴熟运用 Xcode 的开发者来说,创立断点、使断点无效是一件再简略不过的工作,只需求的源代码的左边行数点击即可。可是在 LLDB 还有许多提高开发功率的事,例如 frame、breakpointexpressionimage 等指令。

expression

expression 首要用于「在当时线程履行表达式,并显现其返回值」。其语法如下: expression <cmd-options> -- <expr> 例如被咱们所熟知的 pop 都是关于 expression 的缩写办法

  • poexpression -O -- 的缩写办法
  • pexpression -- 的缩写办法 可以看到,首要有可选参数与表达式两部分;为了区分可选参数与表达式,选用 -- 进行切割,下面罗列常用的一些可选参数:
  • -D,设置最大递归深度解析层级
  • -O,打印特定语言的目标的 description 办法
  • -T,显现变量类型
  • -f,以特定格式化类型进行输出
  • -i,履行表达式时忽略断点触发 更多的可选参数可以经过 help expression 进行检查 一起,expression 还可以可以定义变量,但需在变量名前面参加 $ 标识符,例如

Swift 中:

expression var $width: CGFloat = 20.0

OC 中:

expression NSArray *$array = @[@"one", @"two"];

进程流程控制

Xcode 中的调试工具

当程序运转或暂停时,在控制台上方会呈现上图这 4 个按钮,这 4 个按钮别离对应着「进程暂停与继续」、「履行当时行」、「调入履行函数」、「跳出履行函数」,别离对应着以下 4 个指令:

  1. process continue(continue)
  2. thread step-over(next、n)
  3. thread step in(step、s)
  4. thread step out(fin)

断点关于调试来说是很重要的东西,只需求在 Xcode 源文件左边点击即可增加断点,一起也会呈现在 Breakpoint navigator 中:

Xcode 中的调试工具

一起还可以增加列断点,假如你的一行代码中有几个表达式,你或许期望只停留在某一个表达式中,那么列断点就很有用了,右键想要断点的表达式,点击 Create Column Breakpoint 即可创立列断点。

Xcode 中的调试工具

Breakpoint navigator 中点击左下角的 + 号,可以发现创立有 6 大类型的断点,不过首要来说可以分为两种:

  • 反常、过错断点:捕获反常和过错,在即将产生 Crash 时,提早暂停并定位到有过错的代码中。
  • 符号断点:即 Symbolic Breakpoint,可以经过办法称号创立断点,当履行到对应的办法时,便会暂停。

Xcode 中的调试工具

还可以运用 breakpoint 指令来进行对断点的办理,下面介绍一些常见的指令:

  • breakpoint list 显现断点列表
  • breakpoint enable / disable / del <breakpointId> 经过 id 敞开、封闭、删除断点( id 即为 breakpoint list 显现的 id )
  • breakpoint set <cmd-options>

创立断点的办法有很多种,但最常见的是经过文件名与代码行数创立,或许是符号化进行创立:

  • breakpoint set -f <fileName> -l <lineNum> 经过文件名与代码行数创立
  • breakpoint set -n <function_name> 经过办法名创立

一起还可以在 Breakpoint navigator 中对断点进行编辑,给断点创立称号、断点触发履行条件、暂停前忽略次数、履行 Action,以及履行完 Action 后继续履行。

Xcode 中的调试工具

不过上述的功用都可以经过指令行完结,例如创立履行 Action 与 断点触发履行条件如下:

breakpoint set -C <command> -c <condition expression> -n <function_name>

更多功用可经过 help breakpoint 进行检查。

假如想调查某个值产生变化,那么 watchpoint 会十分有用,相同创立 watchpoint 有 2 种办法,在 debug 时右键特点并点击 watch "<variable-name>"

Xcode 中的调试工具

控制台则可以 watchpoint set variable [-w <watch-type>] [-s <byte-size>] <variable-name> 进行创立。

其他常见的指令

frame 指令可以显现当时栈帧的一些信息:

  • frame info:显现栈帧所在位置
  • frame variable <variableName>:显现栈帧变量,假如没有 <variableName> 则显现栈帧的变量列表,别名 v

thread 用于操作当时进程的一个或多个线程

  • thread list:显现一切线程
  • thread info:显现线程的额外概要
  • thread backtrace :显现线程的调用栈
  • thread continue:继续履行一个或多个指定线程
  • thread exception:显现线程反常目标
  • thread return:提早返回一个栈帧,并可供给可选返回值

process 在当时渠道与进程交互

  • process continue:继续履行当时进程中的一切线程
  • process interrupt:中止当时进程
  • process kill:完毕当时进程
  • process status:显现当时进程状态

image 可以拜访目标模块的信息(是 target modules 的缩写)

  • image list:列出当时可履行和依靠的同享库镜像
  • image lookup:依据参数查找其在可履行和依靠的同享库镜像的信息(如:地址、文件名、办法名、符号等)
  • image search-paths:查找路径的装备项
  • image show-unwind:显现函数组成的 unwind 指令

disassemble 显现当时 target 中的指定汇编指令,默许是当时线程和当时栈帧中的当时办法

  • disassemble:当时线程和当时栈帧中的当时办法的汇编指令
  • disassemble -a <address-expression>:从某一地址开端
  • disassemble -n <function-name>:从某一办法开端

最终,还可以使用 commond alias 或许编写 python 脚本来完结自己的 LLDB 指令。

po & p & v

popv 都可以用来打印变量,那么它们有什么不同呢?

  • po 显现目标的 debugDescription 特点,体系会供给默许值,可以经过完结 CustomDebugStringConvertible 协议进行自定义。

po 后边跟表达式,因此可以履行办法,赋值等操作。po 的履行过程分为两部分,第一步生成源代码,并在上下文中编译履行,第二步获取第一步返回的目标,并再次生成源代码并在上下文中编译履行,最终显现第二步返回的字符串。这儿需求留意的是,为了可以使你的表达式可以被完整表达,LLDB 没有采取直接解析和评估表达式自身,选用生成可编译的源代码进行处理,这种办法完全保留了代码自身。例如,你输入 po view

第一步生成的源代码为:

func __lldb_expr() {
   __lldb_res = view
}

第二步生成的源代码为:

func __lldb_expr2() -> String {
   return __lldb_res.debugDescripution
}

Xcode 中的调试工具

  • p 指令,ppo 的输出略有不同,但都包含相同的信息,每个表达式成果都会被赋予增值称号,如 $R1$R2 等,这些成果就会被存储起来,并可以像一般的目标相同运用。p 指令履行分为 3 步,第一步与 po 指令相同,将表达式生成源代码,并进行编译履行,之后会进行动态类型解析,并将解析成果格式化。动态类型解析是由于其多态性,只要在运转时才能得知其运转时类型;对解析成果进行格式化是由于 Swift 规范库即使针对 IntString 这样的简略类型,都进行了高度封装优化,因此其有杂乱表达,所以需求进行格式化。

Xcode 中的调试工具

  • v 指令,v 指令的输出与 p 完全相同。但与 ppo 不同的是,v 指令并不进行编译与履行代码,所以它十分快,它选用点和下标符来拜访字段。v 指令履行分为 4 步,首要会查询进程状态为了内存中定位变量,之后便从内存中读取变量,并对其履行动态类型检查,假如它有拜访子特点,则屡次进行内存读取变量以及动态类型检查。最终将成果进行格式化。

Xcode 中的调试工具

Debug View Hierarchy

首要从 Xcode 中的 Product -> Scheme -> Edit Scheme -> Diagnostics 中敞开 Malloc Stack Logging 选项,并挑选 All Allocation and Free History。这敞开了创立仓库信息调用日志,在 Debug 时便可经过目标的信息去检查其调用仓库。

翻开 Debug View Hierarchy,便可发现在右侧的 Backtrace 中有了内容,假如你有束缚抵触,或许想检查某个视图的创立信息,只需求在左边的图层结构或许中间的图像选中你想要的即可。

Xcode 中的调试工具

相同的 LLDB 还给咱们带来了愈加强壮的功用,可以达到不需求重新编译然后改动视图的一些行为,详细完结办法可以相似如下:

  1. 定位到某个详细的目标,即从界面中选中某一个视图或许束缚。
  2. 按钮 commond + c 便可仿制其带有类型的内存地址,这时便可以对它进行操作,详细的在控制台中,输入你想要改动的操作,如:

e [((UIView *)0x7fa9320061a0) setBackgroundColor: [UIColor greenColor]]

留意这儿需求运用 Objective-C 的语法,由于 Swift 的安全性导致不能拜访一切内容。

  1. 这时你发现界面没有改动,需求改写视图:

e (void) [CATransaction flush];

详细关于 Debug View Hierarchy 的更多用法可参阅这儿。

Debug Memory Graph

点开 Debug Memory Graph,会暂停进程,并显现当时堆的一切目标,并且会显现它们之间的所属关系和强引证与弱引证(深色的为强引证,淡色的为弱引证)。

假如你敞开了 Malloc Stack Logging,也相同能看见目标的仓库调用信息。

不仅如此,还可以发现内存走漏,可以点开左下角的感叹号,仅筛选出内存走漏目标。不过令人遗憾的是,Debug Memory Graph 并不能显现出一切的内存走漏问题。例如下图,在 SecondViewController 持有 blockblock 中去持有 SecondViewController 中的 view,这是经典的由 block 导致的循环引证,尽管 Debug Memory Graph 没有明确捕捉到,可是仍给咱们排查供给了线索。

Xcode 中的调试工具

点击 Edit -> Export Memory Graph,可以导出内存分布图文件。使用 vmmapleaksheap 等指令行工具可以进一步分析内存问题,详细分析可参阅 iOS 内存调试篇 — memgraph。

Instruments

Instruments 供给了一套丰厚工具和模版去分析 使用的性能问题,常见的模版有:

称号 功用
Leaks 一般的检查内存运用情况,检查走漏的内存,并供给了一切活动的分配和走漏模块的类目标分配计算信息以及内存地址历史记载。
Time Profiler 履行对体系的 CPU上运转的进程低负载时刻为根底采样。
Allocations 盯梢过程的匿名虚拟内存和堆的目标供给类名和可选保留/开释历史。
Activity Monitor 显现器处理的 CPU、内存和网络运用情况计算。
Blank 创立一个空的模板,可以从 Library 库中增加其他模板。
Core Data 监测读取、缓存未命中、保存等操作,能直观显现是否保存次数远超实际需求。
Network 盯梢 TCP/IP 和 UDP/IP 衔接。
Engergy Log 使用的电量消耗情况。

下面基于 Time Profiler 模版,整理怎么运用 Instruments

  1. 首要选中 Time Profiler,会呈现空的装备页
  2. 在左上方中挑选分析的设备以及使用
  3. 点击开端,这时便可操作测试你的使用。
  4. 当操作完结,点击暂停或完毕,这时便可针对有问题的数据进行分析。

Xcode 中的调试工具

选取你认为可疑的时刻段,例如很多占用 CPU 的时刻段,其次逐步依据去排查代码问题,例如主线程中有耗时操作。

更引荐看下 WWDC 中关于 Instruments 的介绍 WWDC2019 – Get Started with Instruments,笔者仅仅简略的概述。关于 Instruments,它没有记载一切的调用栈帧,而是在每秒去记载许屡次栈帧快照,这是为了更好的性能体验。

无限调试

还有一个关于调试的小技巧,假如你期望不运用数据线衔接电脑,可以选用局域网的办法衔接,相同也可以进行真机运转与调试。详细在 Device 列表中右键设备并点击 Connect via IP Address

Xcode 中的调试工具

除此之外在菜单栏中选中 DebugAttach to Process by Pid or Name 或许 Attach to Process ,经过列表选中想要附加的进程,就可以不必想要 Debug 的时候再次手动 Run 一次。不过这两种办法,都会有必定性能损耗,会导致响应时刻慢问题。

总结

本文总结了 LLDB 的一些常见指令,据计算,一名程序员大约有 70% 的时刻都在 Debug,假如可以娴熟运用它们,无疑会极大提高编程功率。一起还介绍了如 Debug View HierarchyDebug Memory Graph 的常见用法。特别是 Debug Memory Graph,由于内存问题往往是不易察觉且不易找到的,好好使用它,可以让咱们对内存问题研究的愈加深化。最终是 Instruments,它里边有许多工具,针对各方面的性能问题都有所涵盖,Jonathan Levin 称,与其他操作体系比较,Instruments 是最好的调试和性能分析工具。

与调试器共舞 – LLDB 的华尔兹

用 LLDB 调试 Swift 代码

WWDC2018 – 412 经过 Xcode 和 LLDB 进行高级调试

WWDC2019 – 429 LLDB 不限于 “po”

WWDC2019 – 411 Get Started with Instruments

《深化解析 Mac OS X & iOS 操作体系》