iOS点点滴滴iOS Developer程序员

IOS 问题调试与处理

2017-04-27  本文已影响661人  就叫yang

之前收集了一些Xcode控制台的一些调试方法,现在结合代码调试和问题查找统一整理一下。

首先,说到调试,分为开发过程中的调试和开发结束自测或者优化阶段的调试:

开发期:LLDB调试、Xcode断点调试等

优化期: instruments 内存优化、运行时间优化等

1、LLDB调试

LLDB是Xcode自带的一个开源调试器。存在于主窗口底部的控制台中,我们开发时间断点调试配合LLDB命令调试为多,文中只介绍常用命令,其余可以直接到文档搜索,或者根据命令直接 终端可以输入help查询具体参数使用。

详细文档

暂停方式:
1.断点
2.控制台上方有一个暂停按钮,点击即可暂停程序

LLDB常用命令

1.expression

expression 可简写为e,作用为执行一个表达式,可以用来查询当前堆栈变量的值。查询的时候和p是一样的。

当然e的更主要的用法是通过执行表达式,动态修改当前线程堆栈变量的值,从而达到调试的目的(其实查询也很主要,只是会用另一种方式查询)。

e viewController.view.backgroundColor = [UIColor blackColor] 
//动态将我们后面要跳入的控制器背景色改变
if(result) {
 //do sth
} else {
 //do sth
}
我们也可以在某个if..else..的语句前打上断点,直接修改条 result 的值,使程序覆盖了不同分支:
e self.result = true
而不用代码修改变量值、多次编译执行来进行调试,节省了修改与编译时间。

2.p、po & print & call

print: 打印某个东西,可以是变量和表达式

(lldb) print self
(ViewController *) $0 = 0x0000618000003880

p: 可以看做是print的简写 和 expression 一样

(lldb) p self
(ViewController *) $1 = 0x0000618000003880

po : 打印一个对象

(lldb) po self
<ViewController: 0x618000003880>

call: 调用某个方法。

call [self.view setBackgroundColor:UIColor.redColor]

3.thread backtrace & bt 、 frame

有时候我们想要了解线程堆栈信息,可以使用thread backtrace 作用是将线程的堆栈打印出来。

thread backtrace   简写  bt

当发生crash的时候,我们可以使用thread backtrace查看堆栈调用 bt 是缩写别名。

打印出来的结果其实和我们Xcode侧边Thread调试的堆栈调用信息是一样的。

bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x0000000103d239b8 OcDemo`-[ViewController viewDidLoad](self=0x0000618000003880, _cmd="viewDidLoad") at ViewController.m:44
    frame #1: 0x00000001065f8d51 UIKit`-[UIViewController loadViewIfRequired] + 1235
    frame #2: 0x00000001065f919e UIKit`-[UIViewController view] + 27
    frame #3: 0x00000001064ccd17 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 122
    frame #4: 0x00000001064cd41f UIKit`-[UIWindow _setHidden:forced:] + 294
    frame #5: 0x00000001064e02bf UIKit`-[UIWindow makeKeyAndVisible] + 42

在上面打出的堆栈调用信息中可以看到很多frame(帧) 开头的段落。

frame 可以使用的命令

frame variable ,可以打印出当前frame的所有变量 如果需要打印frame中得指定变量,也可以在后面跟参数

(ViewController *) self = 0x0000618000003880
(SEL) _cmd = "viewDidLoad"

frame select ,根据frame队列号选中堆栈调用列表中得frame

(lldb) frame select 0
frame #0: 0x0000000103d239b8 OcDemo`-[ViewController viewDidLoad](self=0x0000618000003880, _cmd="viewDidLoad") at ViewController.m:44
   41   }
   42   
   43   - (void)viewDidLoad {
-> 44       �[4m[�[0msuper viewDidLoad];
   45   
   46   }

frame info ,查看当前frame的信息

frame info
frame #0: 0x0000000103d239b8 OcDemo`-[ViewController viewDidLoad](self=0x0000618000003880, _cmd="viewDidLoad") at ViewController.m:44

不过以上操作都可以在Xcode的侧边Thread堆栈调用列表操作,所以使用也较少。

4.c & n & s & finish

c/ continue/ thread continue: 这三个命令都表示程序继续运行
n/ next/ thread step-over: 这三个命令表示单步运行
s/ step/ thread step-in: 这三个命令效果表示进入某个方法
finish/ step-out: 这两个命令效果表示直接走完当前方法,返回到上层frame

5.breakpoint

breakpoint set 设置断点

参数 :

-n 根据当前类中方法名设置断点

(lldb) breakpoint set  -n viewDidLoad
Breakpoint 6: 2 locations.

-f 根据我们指定文件设置断点

(lldb) breakpoint set -f VcTwo.swift -n viewDidLoad
Breakpoint 8: 2 locations.

-l 根据文件某一行设置断点 和 -f配合使用

(lldb) breakpoint set -f VcTwo.swift -l 35
Breakpoint 12: where = SWIFTDEMO`SWIFTDEMO.VcTwo.viewDidLoad () -> () + 679 at VcTwo.swift:35, address = 0x0000000109f08637

-c 设置条件断点

breakpoint set -n goS -c flag == YES

-o 设置单次断点

breakpoint set -n goS -O

breakpoint list 断点列表

(lldb) breakpoint list
Current breakpoints:
1: names = {'objc_exception_throw', '__cxa_throw'}, locations = 2, resolved = 2, hit count = 0
  1.1: where = libobjc.A.dylib`objc_exception_throw, address = 0x000000010594af11, resolved, hit count = 0 
  1.2: where = libc++abi.dylib`__cxa_throw, address = 0x0000000109314b86, resolved, hit count = 0 
2: file = '/Users/young/Desktop/demo/work/OcDemo/OcDemo/ViewController.m', line = 44, exact_match = 0, locations = 1, resolved = 1, hit count = 1
  2.1: where = OcDemo`-[ViewController viewDidLoad] + 216 at ViewController.m:44, address = 0x0000000103d239b8, resolved, hit count = 1 

breakpoint disable/enable 暂停/恢复断点

(lldb) breakpoint disable 2
1 breakpoints disabled.

breakpoint delete 删除断点

(lldb) breakpoint delete 1
1 breakpoints deleted; 0 breakpoint locations disabled.

删除所有断点

(lldb) breakpoint delete 
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (6 breakpoints)

如果文件不存在或者方法不存在

Breakpoint 11: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.

6.其他

开启调试模式、打印出所有运行时发送的消息: 可以在代码里执行下面的方法:

(void)instrumentObjcMessageSends(YES);

或者断点暂停程序运行,并在 gdb 中输入下面的命令:

call (void)instrumentObjcMessageSends(YES)

之后,运行时发送的所有消息都会打印到/tmp/msgSend-xxxx文件里了。

终端中输入命令前往:

open /private/tmp

可能看到有多条,找到最新生成的,双击打开

在模拟器上执行执行以下语句(这一套调试方案仅适用于模拟器,真机不可用,关于该调试方案的拓展链接: *Can the messages sent to an object in Objective-C be monitored or printed out?*

2、Chisel调试

Chisel扩展了一些列的lldb的命令来帮助iOS开发者调试iOS应用程序。

1.安装Chisel

1.确保终端安装了Homebrew

2.终端执行命令:brew install chisel 输入命令后我遇到第一个问题。

brew install chisel 后可能出现的问题
碰见这个问题终端执行命令:sudo chown -R ${USER} /Library/Caches/Homebrew/,执行此命令后问题解决。

3.如果没有第二步的问题,执行命令:brew install chisel后出现如下界面

brew install chisel 执行成功
4.注意看Caveats下面的那两行,意思是把第二行的文字command script import /usr/local/opt/chisel/libexec/fblldb.py添加到.lldbinit文件中,这时执行命令echo command script import /usr/local/opt/chisel/libexec/fblldb.py >> ~/.lldbinit(粗体文字替换为你终端Caveats下面的第二行文字)可免去你去找.lldbinit文件,或者.lldbinit文件不出现的烦恼啊。到此步不出意外已经安装成功。

5.安装成功后重新启动Xcode即可。

6.xcode检查是否安装成功,打断点,控制台输入help. 终端下检查是否安装成功输入命令:lldb,然后输入help

Current user-defined commands:

  alamborder   -- Put a border around views with an ambiguous layout
  alamunborder -- Removes the border around views with an ambiguous layout

出现这个就表示安装成功,可以使用了。

2.Chisel的使用

1.pviews

这个命令可以递归打印所有的view,并能标示层级,相当于 UIView 的私有辅助方法 [view recursiveDescription] 。 善用使用这个功能会让你在调试定位问题时省去很多麻烦。可以直接根据view名称或者内存地址去查找对应的层级view

pviews 0x7f88ae7a08c0
<UIView: 0x7f88ae7a08c0; frame = (0 0; 320 64); autoresize = LM+RM+TM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x600005a23d40>>

2.pvc

这个命令也是递归打印层级,但是不是view,而是viewController。利用它我们可以对viewController的结构一目了然。 其实苹果在IOS8也默默的添加了 UIViewController 的一个私有辅助方法 [UIViewController _printHierarchy] 同样的效果。而且还可以看到 viewController 是否已经 viewDidLoad 。

3.fv & fvc

fvfvc 这两个命令是用来通过类名搜索当前内存中存在的view和viewController实例的命令,支持正则搜索。

fv UI
0x7f88ae55f830 UILayoutContainerView
0x7f88ae562120 UINavigationTransitionView
0x7f88ae5d44c0 UIViewControllerWrapperView
0x7f88ae43fec0 UILayoutContainerView

4.visualize

这是个很有意思的功能,它可以让你使用Mac的预览打开一个 UIImage, CGImageRef, UIView, 或 CALayer。 这个功能或许可以帮我们用来截图、用来定位一个view的具体内容。 但是在我试用了一下,发现暂时还是只能在模拟器时使用,真机还不行。比如说知道一块内存地址,这种情况下可以用这个命令直接去看截图效果,更容易知道对应的位置。

visualize 0x7f88ae728e20
或
visualize self.xxxlabel

5.show & hide

这两个命令用来显示和隐藏一个指定的 UIView . 你甚至不需要Continue Progress. 就可以看到效果。

6.mask/umask border/unborder

这两组命令用来标识一个view或layer的位置时用, mask用来在view上覆盖一个半透明的矩形, border可以给view添加边框。但是在我实际使用的过程中mask总是会报错,估计是有bug, 那么mask/unmask 一般不要用好了,用border命令是一样的效果,反正二者的用途都是找到一个对应的view。

7.caflush

这个命令会重新渲染,即可以重新绘制界面, 相当于执行了 [CATransaction flush] 方法,要注意如果在动画过程中执行这个命令,就直接渲染出动画结束的效果。

当你想在调试界面颜色、坐标之类的时候,可以直接在控制台修改属性,然后caflush就可以看到效果啦,是不是要比改代码,然后重新build省事多了呢。

3、instruments使用

iOS性能优化:Xcode自带 Instruments工具, Instruments提供了很多功能,常用的主要是内存检测和代码执行时间分析。

打开方式:Xcode - Open Developer Tool - Instruments

符号化问题:

当跟踪数据里面显示的是地址而不是可读性较强的符号

image

因此需要将地址转换为符号。地址和 符号的映射保存在dSYM文件中。instruments工具一般会自动找到dSYM文件,默认在当前电脑build的项目是可以正常使用的。

如果其他方式的包进行调试需要配好对应的 dSYM文件,可以手动设置(暂停调试):files -> symbols > 选择需要符号化的可执行文件或者framework > “select dSYM or containing folder” > 找到dSYM文件(dSYM文件默认会保存在build文件夹下)

Timer Profiler : 分析代码的执行时间

TimeProfiler见名知意:时间分析工具,它会按照设定的时间间隔(默认1毫秒)来跟踪每一线程的堆栈信息(stacktrace),并通过比较时间间隔之间的堆栈状态,来推算出某个方法执行了多久,给出一个近似值。具体步骤如下:

1、双击TimeProfiler进入到调试界面

2、选择机器和要调试的App(最好选择真机,性能参数更真实,而且Xcode9模拟器经常跑不了)

3、点击左上角红色按钮开启调试之后便可以看到如下图的时间消耗

image

但是整个执行过程包括了很多我们不需要看到的系统进程等,所以我们通过底部的 Call Tree 来做一些筛选过滤:

image

Separate by Thread:按线程分开做分析,这样更容易揪出那些吃资源的问题线程。特别是对于主线程。

Invert Call Tree:反向输出调用树。把调用层级最深的方法显示在最上面,更容易找到最耗时的操作。

Hide Missing Symbols:隐藏缺失符号。如果dSYM文件或其他系统架构缺失,列表中会出现很多奇怪的十六进制的数值,用此选项把这些干扰元素屏蔽掉,简化列表。

Hide System Libraries:隐藏系统库文件。过滤掉各种系统调用,只显示自己的代码调用。

Flattern Recursion:拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。

Top Functions:找到最耗时的函数或方法。

将以上勾选之后便可以看到对应的具体代码,具体方法的执行时长,并进行优化。

Leaks : 内存泄漏检测工具

除开代码运行时间分析,我们还常用的就是内存检测,灵活的运用Leaks可以帮助我们预防程序中的内存泄漏防止程序内存耗用过大被挂起。

具体操作:

1、双击Leaks进入到调试界面

2、选择机器和要调试的App(最好选择真机,性能参数更真实,而且Xcode9模拟器经常跑不了)

3、点击左上角红色按钮开启调试之后使用App,观察Leaks Checks 栏,当出现红叉,就表示有内存泄漏点:

image

4、在上图可以看到我们选中了Leak Checks栏,然后在中间的工具栏切换到Call Three,同上时间分析,我们需要设置一些过滤条件,过滤掉一些妨碍我们观察的内容:

如上图是已过滤后,剩下都是用户写的代码,选中其中一个双击或者 右击 reval to xcode,可以直接跳转到对应代码区域,看代码高亮两行就是内存泄漏点,如下:

image
上一篇 下一篇

猜你喜欢

热点阅读