iOS 调试技能 - bug定位、性能调试、常见问题分析

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 输出日志,这个时候就可以考虑使用单步调试:

ADD2B91E-EA8A-4C0B-976A-9E9BE78C27E9.png

单步调试的时候可以逐步查看代码的执行过程,是解决 bug 的神器,如果你是一位新手,这肯定是你首要学会的技能,在单步调试的时候,一般都会结合 LLDB 命令来使用,关于 LLDB 的内容下面会详细说。

但在实际开发中,有些场景是无法通过单步调试来复现的,比如说多线程的场景下,在使用单步时,很多时候是无法复现真实场景的,这个时候就需要使用万能的 print 了。总之,两种方式是必不可少的,在实际开发中,很多时候我们不会直接使用 print,一般都会使用日志框架,来对日志进行和记录和收集。

Crash Report

在我们平常的开发中,你提交测试包给 QA 测试时,他那边出现在了 crash,但却不是调试模式下,这里我们就可以通过通过测试设备,在 Xcode 的 Devices 中把 crash 日志导出来:

5786A795-FC55-4A6F-8C3D-8CFE014257CC.png

关于如何去阅读 Crash report 和定位该 Crash 原因,可以看我之前写的文章:浅谈 Crash Report,这里就不再重复谈。

dSYM 文件

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

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

应用上架后,可以通过类似友盟统计等工具收集线上的 Crash,这里直接以友盟的为例,先看下 Crash 信息:

601D48A0-954A-4A0B-9AE3-252E7C966999.png

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

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

这时就可以通过 dSYM 文件来定位出问题的地方了。首先通过 archives 来找到 dSYM 文件:

步骤1:


步骤1.png

步骤2:


步骤2.png

步骤3:


步骤3.png

我们 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 <command> 可以输出相应命令的 help。

    图片.png
  1. po、p 打印值

    图片.png

    po 和 p 的区别在于使用po只会输出对应的值,p 则会返回值的类型以及命令结果的引用名。

  2. exp 输出或修改值(主要作用是修改值)

    图片.png
  3. bt 当前线程的调用堆栈,可能通过后面添加数字来限制输出线程数,如 bt 5,只输出前5个。

    52245C67-3376-4A95-A323-A3875BEBF2F9.png
  4. thread return 跳出当前方法的执行(thread return 0 设置返回值),但在 swift 中,是无法使用的,已知的问题了,只能等待修复吧,这里给个 OC 的例子:

    51084CDC-031A-490C-A0BE-474DA5C6B423.png

3. Instrument 的使用

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

Time Profiler

time profile 是时间分析工具,主要用来检测应用 CPU 的使用情况,可以看到应用程序中各个方法消耗 CPU 时间。关于概念,这里就不详细介绍了,直接进行实际操作:

  1. 通过 xcode 中的 product --> profile 来启动 Instrument,并选择 Time Profiler 工具:

    BAE9B1B4-19D3-447D-9D59-42955BC22114.png
  2. 运行 Time Profiler,配置显示方式,分线程显示和隐藏系统的无关内容:

    B6CA42E2-0AEC-4787-AA39-C1B48FD7CF1F.png
  3. 在手机上执行想要测试的操作,执行完后停止 Time Profiler 进行分析:

    CBE4F6B3-6DD8-4CC0-8280-8F15A599B425.png
  4. 找到主要耗时的地方,并定位到具体的代码行(点击方法的小箭头就可以进入相应的代码处):

    11C6F965-91F3-4A44-ACF8-1F2FC03F2DC3.png

    这里可以看出,主要有两一个耗时的操作,但明显后面那我格式转换我们没有办法去处理,我们只能从第一个入手。它每次创建都比较耗时,那么我们就不要多次去创建,因为它每次使用的格式的都是一样的,这样我们实质上只需要创建一次就可以了。那我们有什么方法去只创建一次呢,首先能想到的肯定是单例,但是用单例太麻烦了,通过 static 定义成一个常量就可以了,就这样,这处的性能问题就解决了,其它地方也可以通过同样的方法,逐步分析和解决就可以了。

Leaks

使用的步骤几乎和上面的一样,这里就不重复上图了,但在出现内存泄露的地方,我们需要手动去选取对应的位置,这样才方便分析问题:

3404A8AC-066D-4800-89BE-9DB013D2B2B0.png

因为 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 的华尔兹
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容