作为一名程序员,日常开发过程中少不了各种代码调试,这也是程序员的必备技能之一,今天便来简单的聊一聊iOS开发过程中的一些调试技巧。
工具准备:Mac,iPhone,Xcode,Charles,Reveal
1. 断点调试
1.1 常规断点
断点调试是最简单的调试方式之一,在要调试代码行的左侧单击或者代码光标处使用快捷键(command+\
)添加断点,运行项目满足断点条件后会在断点所在的代码行处停住,此时可以配合控制台LLDB调试进行代码调试。运行图示如下:
单击断点标识可以停用断点,右击断点标识可以对断点进行更多操作,比如删除,编辑等等。
1.2 异常断点
代码运行过程中可能会出现各种崩溃,崩溃后Xcode代码会卡在main.m文件的mian函数这,对于崩溃调试是没有任何帮助的。那么如何捕捉这种崩溃所发生的代码位置呢,这就要用到异常断点了。
Xcode
中选中导航中的断点按钮,点击左下角+
按钮,选择Exception Breakpoint...
选项,给当前项目增加异常断点,如下图所示:
断点增加完后就是图中左边的All Objective-C Exception
断点,此时项目运行一旦发生崩溃,Xcode
会将程序暂停在问题代码所在行处,如图所示:
图中代码模拟了一个简单的crash,给不是NSString
对象的url调用了一个NSString
对象的length
方法,因为增加了异常断点,程序直接暂停在了crash所发生的的代码处,使得问题定位更加简单方便。
1.3 符号断点
从一个案例入手看如何使用符号断点。Xcode控制台输出日志如下:
[framework] CUICatalog: Invalid asset name supplied: '(null)'
问题原因:定位到原因是[UIImage imageNamed:]
传入的参数为nil。但是有一个问题,怎么定位到时哪个文件的哪些[UIImage imageNamed:]
方法传入的参数是nil。这个时候使用Symbolic Breakpoint
断点调试的方法,可以让我们在解决这类问题时事半功倍。
- 点击左下角
+
选择Symbolic Breakpoint...
- 右键断点选择
Edit Breakpoint...
- 在
Symbolic
选项中输入要调试的目标方法,比如[UIImage imageNamed:]
- 在
Condition
选项输入触发目标方法断点的条件,比如$arg3 == nil
运行程序,如果当前界面有调用目标方法,并且条件满足,就会进入这个方法的断点模式,进入Xcode
的Debug Navigator
选项,查看调用堆栈,栈顶的方法就是出现问题的代码,直接点击查看即可。
2. LLDB调试
在断点调试的常规断点中我们说到可以配合控制台LLDB调试方式进行代码调试,这样可以起到事半功倍的效果。图示如下:
Xcode加上普通断点后运行项目,触发断点就可以在控制台看到类似上面图片的内容,左边是当前代码块内的可用变量,右边可以使用LLDB调试器操作变量进行代码调试。
下面看下常用的LLDB调试命令:
2.1 p
、po
打印当前变量值一般使用p
与po
命令。p
等同于print
,会打印出当前变量的变量类型、地址信息和变量信息。po
是最常用的打印命令,会打印出当前变量。
2.2 c
、n
c
就是Continue
,继续执行当前程序。
n
就是StepOver
,一步一步执行当前代码。
2.3 expression
打印表达式的值,不仅改变了调试器中的值,也改变了程序中变量的值。这对于调试来说非常好用,不用重新运行App即可看到修改后的效果,可以为我们的开发调试过程节省大量时间。
(lldb) po url
https://www.google.com.hk/
(lldb) expression url = @"http://www.baidu.com"
(__NSCFString *) $10 = 0x0000000170459a10 @"http://www.baidu.com"
(lldb) po url
http://www.baidu.com
2.4 b
、br
- 给
DDHomeViewController.m
文件第100行加断点。
b DDHomeViewController.m:100
- 打印当前项目所有断点信息。
br l
- 删除第n个断点。
br delete n
2.5 image
image
是用来获取一个或多个目标模块信息的命令。如下,用来查看当前项目中使用到的依赖库:
(lldb) image list
[ 0] 047D2C9A-BBEC-307A-AC41-357643679B5F 0x00000001000bc000 /Users/admin/Library/Developer/Xcode/DerivedData/Demo-fafnyyrbwiblnaapporhwiinjobd/Build/Products/Debug-iphoneos/Demo.app/Demo
/Users/admin/Library/Developer/Xcode/DerivedData/Demo-fafnyyrbwiblnaapporhwiinjobd/Build/Products/Debug-iphoneos/Demo.app.dSYM/Contents/Resources/DWARF/Demo
[ 1] A3339F99-C2EA-39D8-BEB7-0B8FF2E84061 0x00000001032dc000 /Users/admin/Library/Developer/Xcode/iOS DeviceSupport/10.3.2 (14F89)/Symbols/usr/lib/dyld
[ 2] DA0F6A86-DB85-3140-B2D7-9E3B36F28795 0x000000018e07e000 /Users/admin/Library/Developer/Xcode/iOS DeviceSupport/10.3.2 (14F89)/Symbols/usr/lib/libc++.1.dylib
如果还想更加详细的了解LLDB的相关信息,可以参考XCODE LLDB TUTORIAL、The LLDB Debugger。
3. Charles调试
Charles是目前网络调试的主要工具之一,功能类似于Fiddler工具。在日常开发过程中可以用其抓取接口Request的Headers
、Query String
和Cookies
等信息,以及Response的返回JSON Text
数据和数据结构等信息来帮助日常开发调试工作。安装和配置教程网上很多,这里不做赘述,下面我们来看下Charles的常见调试技巧。
3.1 抓取请求,查看请求信息
在安装和配置好Charles的前提下,打开Charles,将手机连接代理到电脑,打开想要抓包的应用,如果有请求的话,就会如上图左侧所示展示出当前手机的所有请求列表,找到你想要的查看的请求,选中后在右侧即可看到这个请求的相关信息。
- 顶部红框,
Overview
项可以查看当前请求的概览信息,比如URL
、Status
、Response
等。Request
项可以查看当前请求的Request
信息,比如Headers
、Query String
、Cookies
等。Response
项可以查看请求的返回信息,比如请求的返回信息等。 - 底部红框,选中
JSON Text
选项即可查看当前请求的返回数据和数据结构。 - 左下角的
Filter
输入框可以帮我们过滤掉不需要关注的请求,只需要输入想要查看请求的关键词即可。
3.2 Map Local
开发过程中如果服务端的接口数据还没有准备好,或者需要修改返回数据的某个值以满足本地调试条件,则可以使用本地调试(Map Local)功能来完成开发调试工作。
- 首先在本地准备一个JSON文件,文件内容是你自己处理好的JSON数据,比如服务端没有写好的数据或者你想要修改的数据都可以放在这个JSON文件中。
- 选中你想要修改返回数据的请求,右击选择
Map Local...
点击Choose
,找到你已准备好的JSON文件,点击Select
,确认配置完毕后点击OK
。
- 刷新请求,这个时候你所拿到的数据就是修改后的本地数据,这对于开发调试来可以起到事半功倍的效果。
3.3 Map Remote
比如在做前端开发的时候搭建了一个本地服务器,开发任务写完后想要把本地环境请求切到外网的测试环境请求,这个时候就可以使用Charles的Map Remote
功能来实现这个需求。
- 选中你想要修改返回数据的请求,右击选择
Map Remote...
- 在弹窗中配置好
Map From
(想要修改的请求)和Map To
(目标请求)模块。 - 点击
OK
刷新请求,这个时候请求的就是Map To
所设置的目标请求了。
3.4 修改网络请求
开发过程中为了达到某种目的,可能会有修改网络请求的Cookie
、Header
等信息的需求,强大的Charles当然也少不了这种调试功能。
- 左侧请求列表中选中要修改参数的请求,点击右侧箭头指向的钢笔按钮(Compose a new request based on the selection)。
- 点击后就可以通过下方的
Add
和Remove
对选中的请求做参数编辑操作了 - 操作完后点击下方的
Execute
按钮,那么根据选中请求构建成一个新请求的操作就会被执行并重新发起请求了。
修改网络请求的操作还可哟通过Breakpoints
功能来完成,在选中的请求上右击选择Breakpoints
,然后点击Charles顶部的刷新按钮,这个时候请求会被中断,在Charles右边的面板上就可以Add
和Remove
请求参数了。
3.5 模拟弱网环境
开发过程中如果想要在数据请求的过程中做一些操作,但是网络请求很快,你还没有操作网络请求已经执行完毕了,这个时候弱网环境模拟功能就排上用场了。
- 在Charles菜单栏选择
Proxy --> Throttle Settings...
,弹窗如图所示:
- 选中
Enable Throttle
选项,即可在弹窗下方设置网络请求参数了,比如带宽(下载和上传)、利用率、可靠性等等。 - 配置完后点击
OK
即可使用配置好的弱网环境开发调试了。
3.6 解析pb格式数据
如果项目中接口使用了ProtoBuf数据,在使用Charles抓包时看到的请求和返回数据将是一堆乱码,可读性极差,对于想获取这个接口信息的人来说是非常不友好的。想要看到真实的请求和返回数据呢,通过在Charles中给该接口配置desc
文件即可,下面看具体实现。
- 生成
desc
文件
进入到proto文件所在目录,执行下面命令,当然命令执行成功的前提已经安装了protoc工具(工具安装可以参考Protobuf在iOS中的使用小结)。
protoc -I=./ --descriptor_set_out=./basic.desc ./basic.proto
- Charles配置
① 在Charles中选中使用pb格式的请求链接,选中Viewer Mappings...
② 选中后弹出配置弹窗,按图中所示,依次填选Request Type
,Message Type
③ 如果Message Type
下拉列表中没有目标文件,点击Open Descriptor Registry
,Adddesc
文件
如上配置完成之后,请求和响应中的pb数据就可以完整的展示出来了。
3.7 常见问题
Charles导入根证书时,钥匙串无反应;下载证书后拖入钥匙串无反应;双击证书添加提示不能修改钥匙串;这三种问题都可以通过重置根证书来解决。Charles中点击Help->SSL Proxying -> Reset Charles Root Certificate
,然后重新添加根证书,设备添加证书并信任就可以了。
4. UI调试
Xcode
自身已经提供了App界面调试工具,运行项目,点击Xocde
的Debug View Hierarchy
按钮,如图所示:
可以看到程序当前界面的视图树和层级关系,所有空间的大小、位置一目了然,对于UI调试十分有利。但是Xcode提供的这个工具仍然不够完美,比如点击某个层级视图时不能和左侧的视图树关联起来,不能迅速找到想要的视图信息等等。不过不用担心,确实还有另一款界面调试神器等着我们,Reveal
!
Reveal
不仅具备展示视图树和层级关系的能力,还允许开发者编辑各种用户界面参数,结果还能再用户界面上实时刷新展示出来。部分功能可以在不修改代码,不重新运行项目的情况下调试iOS应用的App界面。如何安装配置可以参考这篇文章Reveal使用。
是不是很厉害的一款UI调试工具,如果你的iOS设备越狱了,它还可以"调试"其他App的界面,以帮助我们研究学习业界某些精品App的牛X界面效果。
5. H5调试
一个App不可能全由Native
页面组成,可能还会包含由H5
、React Native
、Fluttter
等实现的页面,此处我们来看下如何在App端调试H5
页面。以iOS为例,我们需要Xcode
、iPhone
(或模拟器)和Safari共同完成调试工作。
5.1 配置
- 想要使用Safari完成调试工作,首先要设置将其设置为开发模式。打开Safari浏览器,在菜单栏选择
Safari浏览器 --> 偏好设置...
,弹窗中选择高级
选项卡,勾选在菜单栏中显示“开发”菜单
选项。
-
iPhone
也需要设置,打开设置 --> Safari浏览器 --> 高级
,将网页检查器
开关打开。
5.2 调试
- 使用
Xcode
运行项目,在iPhone
或模拟器上打开想要调试的H5
页面。 - 打开Safari浏览器,在菜单栏选择
开发 --> iPhone/模拟器 --> 要调试的H5页面
。
- 在弹出的调试页面就可以做调试工作。
6. React Native调试
如果项目中使用React Native
开发了某些页面和模块,那么React Native
页面的调试也是必不可少的,下面我们来看下如果使用Xcode
和Chrome
来完成React Native
代码调试。
6.1 开启调试模式
Xcode
运行项目打开RN页面,真机使用摇一摇,模拟器使用Command+D
打开Developer Menu
,可以看到开发者菜单栏。
-
Reload
,将项目中的JS
代码部分重新生成bundle,然后传输给真机或者模拟器。 -
Debug JS Remotely
,使用Chrome远程调试RN代码,支持断点调试。 -
Enable Live Reload
,顾名思义JS
代码改动后可以实时自动生成bundle传递到真机或者模拟器上。改完代码后立马可以看到效果,体验更好。 -
Enable Hot Reloading·
,JS代码改动后每次保存的时候,Hot Reloading功能便会生成此次修改代码的增量包,然后传输到真机或模拟器上实现热加载。
6.2 Debug JS Remotely
-
Xcode
中,在React Native
代码文件,将localhost
改为电脑的IP
地址。运行项目打开RN页面。
XXReactNativeController *deatilVC = [[self alloc] init];
#if DEBUG
NSURL *jsCodeLocation = [NSURL URLWithString:@"http://10.220.38.114:8081/index.bundle?platform=ios"];
#else
NSURL *jsCodeLocation = [NSURL fileURLWithPath:[DDFileTool rn_proudct_bundlePath]];
#endif
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
properties[@"type"] = @(type);
properties[@"product_id"] = productID;
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:kRNModuleName initialProperties:@{@"nativeData":properties} launchOptions:nil];
deatilVC.view = rootView;
- 在
Developer Menu
选中Debug JS Remotely,Chrome会自动为我们打开远程调试窗口,使用快捷键Command+Option+I
打开开发者工具。
打开开发者工具后即可看到上面图片内容,中间是你的本地RN文件,右侧是代码调试区。在右边区域选中Sources
选项卡就可以看到所要检查RN页面的所有代码,并且整个面板提供了暂停、恢复、步进等调试功能以及控制台log输出区域。具体功能细节如图所示:
如果还想了解更多调试技巧,可以参考React Native调试技巧与心得这篇文章。
7. Flutter调试
Flutter
调试在上篇文章Flutter混编方案已经介绍过了,有兴趣的同学可以移步去了解一下。