1. 常用调试办法

Print VS 单步调试

说到调试,刚入门编程时,用得最多的无疑是 print,毕竟连教材都是这样写的,直接打印,简略明了。可是当打印内容太多时,就简略看得头晕脑胀了,这儿以 Swift 为例,略微改进下 print 办法:

/**
print log
#file       String    包括这个符号的文件的途径
#line       Int       符号呈现处的行号
#column     Int       符号呈现处的列
#function   String    包括这个符号的办法姓名
*/
func printLog<T>(_ message: T,
                    file: String = #file,
                    method: String = #function,
                    line: Int = #line)
{
    #if DEBUG
    print("((file as NSString).lastPathComponent)[(line)], (method): (message)")
    #endif
}

打印的时分输入文件的途径,队伍号和办法明等,这样更便利经过 print 的日志来精准定位问题。可是经过 print 来调试时,有时分就不简略发现一些逻辑上的问题,或者需求运用许多的 print 输出日志,这个时分就能够考虑运用单步调试:

iOS 调试技术   bug定位、功能调试、常见问题剖析

单步调试的时分能够逐步查看代码的履行过程,是处理 bug 的神器,假如你是一位新手,这肯定是你首要学会的技术,在单步调试的时分,一般都会结合 LLDB 指令来运用,关于 LLDB 的内容下面会具体说。

但在实践开发中,有些场景是无法经过单步调试来复现的,比如说线程的场景下,在运用单步时,许多时分是无法复现真实场景的,这个时分就需求运用全能的 print 了。总之,两种办法是必不可少的,在实践开发中,许多时分咱们不会直接运用 print,一般都会运用日志结构,来对日志进行和记载和搜集。

Crash Report

在咱们往常的开发中,你提交测验包给 QA 测验时,他那边呈现在了 crash,但却不是调试模式下,这儿咱们就能够经过经过测验设备,在 Xcode 的 Devices 中把 crash 日志导出来:

iOS 调试技术   bug定位、功能调试、常见问题剖析

关于怎么去阅读 Crash report 和定位该 Crash 原因,能够看我之前写的文章:浅谈 Crash Report,这儿就不再重复谈。

dSYM 文件

首要来科普下什么是 dSYM 文件:

Xcode编译项目后,咱们会看到一个同名的 dSYM 文件,dSYM 是保存函数地址映射信息的文件,调试的 symbols 都会包括在这个文件中,并且每次编译项目的时分都会生成一个新的 dSYM 文件。

应用上架后,能够经过类似友盟计算等东西搜集线上的 Crash,这儿直接以友盟的为例,先看下 Crash 信息:

iOS 调试技术   bug定位、功能调试、常见问题剖析

这儿能够看出这个错误的原因是数组越界了,那么问题来了,咱们并不知道是哪里越界,上面只给出了一个内容地址:

5   YHRSS                        0x1000420b0 YHRSS + 270512
6   YHRSS                        0x100041378 YHRSS + 267128

这时就能够经过 dSYM 文件来定位出问题的当地了。首要经过 archives 来找到 dSYM 文件:

过程1:

iOS 调试技术   bug定位、功能调试、常见问题剖析

过程2:

iOS 调试技术   bug定位、功能调试、常见问题剖析

过程3:

iOS 调试技术   bug定位、功能调试、常见问题剖析

咱们 cd 到该文件目录下,然后履行:

atos -arch arm64 -o YHRSS 0x1000420b0

留意这儿的 -arch 是和上面 crash 报告中的对应,否则是看不到相应的信息的:

$ atos -arch arm64 -o YHRSS 0x1000420b0
specialized YHArticlesViewController.tableView(_:heightForRowAt:) (in YHRSS) (YHArticlesViewController.swift:215)

这样咱们能就精准地获取 crash 呈现的具体位置,然后就该是发挥自我价值的时分了。

那些项目中遇到的常见问题定位

循环引证快速定位和处理

假如你置疑存在循环引证,你能够 Instrument 东西来定位,但这也太麻烦了,你能够直接在 deinit{} 办法( OC 中对应的就是 dealloc 办法)里边打一个断点,假如页面退出时没有履行到该处,就阐明该页面存在循环引证,页面内存没有办法开释。

假如存在循环引证,那么首要要查看的是 block 里边的 self 是不是需求 weak,自界说的 delegate 是不是写成了 strong,绝大部分都是这两个原因导致的,逐个去查看就好。

2. LLDB 常用指令的运用

###什么是 LLDB LLDB 是 Xcode 内置的调试东西,它与 LLVM 编译器一同,给开发者提供更丰厚的流程控制和数据检测的调试功能,它的首要功能是为 Xcode 提供底层调试环境。

常用指令

  1. help 最牛逼的指令 help 能够输出 LLDB 的指令,运用 help 能够输出相应指令的 help。

    iOS 调试技术   bug定位、功能调试、常见问题剖析

  2. po、p 打印值

    iOS 调试技术   bug定位、功能调试、常见问题剖析

    po 和 p 的差异在于运用po只会输出对应的值,p 则会返回值的类型以及指令结果的引证名。

  3. exp 输出或修正值(首要作用是修正值)

    iOS 调试技术   bug定位、功能调试、常见问题剖析

  4. bt 当前线程的调用仓库,或许经过后边增加数字来约束输出线程数,如 bt 5,只输出前5个。

    iOS 调试技术   bug定位、功能调试、常见问题剖析

  5. thread return 跳出当前办法的履行(thread return 0 设置返回值),但在 swift 中,是无法运用的,已知的问题了,只能等待修复吧,这儿给个 OC 的例子:

    iOS 调试技术   bug定位、功能调试、常见问题剖析

3. Instrument 的运用

写在最前面,在做功能测验的时分,不能用模拟器,用真机,用真机,用真机,重要的事情说三次。

Time Profiler

time profile 是时刻剖析东西,首要用来检测应用 CPU 的运用状况,能够看到应用程序中各个办法消耗 CPU 时刻。关于概念,这儿就不具体介绍了,直接进行实践操作:

  1. 经过 xcode 中的 product –> profile 来启动 Instrument,并挑选 Time Profiler 东西:

    iOS 调试技术   bug定位、功能调试、常见问题剖析

  2. 运转 Time Profiler,装备显现办法,分线程显现和隐藏系统的无关内容:

    iOS 调试技术   bug定位、功能调试、常见问题剖析

  3. 在手机上履行想要测验的操作,履行完后中止 Time Profiler 进行剖析:

    iOS 调试技术   bug定位、功能调试、常见问题剖析

  4. 找到首要耗时的当地,并定位到具体的代码行(点击办法的小箭头就能够进入相应的代码处):

    iOS 调试技术   bug定位、功能调试、常见问题剖析

    这儿能够看出,首要有两一个耗时的操作,但显着后边那我格式转化咱们没有办法去处理,咱们只能从第一个入手。它每次创立都比较耗时,那么咱们就不要屡次去创立,由于它每次运用的格式的都是相同的,这样咱们实质上只需求创立一次就能够了。那咱们有什么办法去只创立一次呢,首要能想到的肯定是单例,可是用单例太麻烦了,经过 static 界说成一个常量就能够了,就这样,这处的功能问题就处理了,其它当地也能够经过同样的办法,逐步剖析和处理就能够了。

Leaks

运用的过程几乎和上面的相同,这儿就不重复上图了,但在呈现内存走漏的当地,咱们需求手动去选取对应的位置,这样才便利剖析问题:

iOS 调试技术   bug定位、功能调试、常见问题剖析

由于 Leaks 的运用和 Time Profiler 是相同的,这儿就不去重复描述运用过程,在这儿简略地介绍下会呈现内存走漏的常见状况:

  1. 循环引证
  2. ARC 中运用 C 办法开辟的内存没有手动开释
  3. URLSession 目标的屡次创立,运用 AFNetworking 时也是同理

这儿独自针对 URLSession 目标的屡次创立会导致内存走漏问题独自阐明下,其实咱们只需看过 URLSession 的文档咱们就知道了:

Important

The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until it exits.

session 目标会一向持有强引证,导致无法开释,屡次创立就会有内存走漏问题,关于 session 的运用,咱们应该运用一个单例来管理。而且仅有的 session 还能够加快网络恳求,如衔接复用等,这儿就不具体说,回头有时刻再独自写一篇相关的文章,毕竟这不是一两句话就能说清楚的事情。

参考文献

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