Xcode 调试方法总结

时间:2024-04-16 16:00:35

编写代码过程中出现错误、异常是不可避免的。通常我们都需要进行大量的调试去寻找、解决问题。这时,熟练掌握调试技巧将很大程度上的提高工作效率。接下来就说说开发过程中Xcode的调试方法。

1. Enable NSZombie Objects (开启僵尸对象)。 
这个技巧主要用来追终重复释放的问题。个人认为,ARC推出以来。项目的基本是基于ARC环境。不用开发者主动去调用release去释放对象,所以不用太在意这个方法。这里就不多做介绍了。想了解该方法的同学请 坐飞机 
2. 断点调试(全局断点、条件断点) 
一、全局断点:

NSArray *aa = @[@2,@4];
NSLog(@"%@",aa[3]);
  • 1
  • 2

这两行代码,没有添加全局断点时,运行crash,直接就跳到了mian函数,如下图:

这里写图片描述

接下来添加全局断点,方法如下图:

添加全局断点的方法

添加之后运行,奔溃后,程序停留在了crash那行代码。

这里写图片描述
是不是很方便,很省事。哈哈!(ps 不过有的crash,这种方式定位不到)

二、条件断点:设置断点触发的条件,方便开发者对特定情况进行调试 
如下图: 
在for循环中添加一个断点。右击断点选择”Edit BreakPoint”,然后设置断点触发条件。 
这里写图片描述

这个例子当 “i==5”时,断点触发,如下图: 
这里写图片描述

3. Static Analyzer (静态分析) 
Static Analyzer主要用于分析内存,避免内存泄漏。主要对以下情况进行分析。 
未使用的实例变量、未初始化的实例变量、类型不兼容、无法达到的路径、引用空指针 
使用:command + shift +B,如下图就能轻松找到可能内存泄漏的代码,然后我们根据代码环境进行修复就可以了(ps:有的内存泄漏可能检测不出来,还是需要我们在写代码时对内存这块多留点心。)

这里写图片描述

4. LLDB调试器 
这个方法是我今天主推的方法。比较高级,也更加灵活、方便。 
随着Xcode5,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器。其实Xcode已经帮我们完成了大部分工作,而且很多东西也可以在Xcode中直接看到。所以这里我们只列举常用的命令。 
打印:p,print的缩写:该命令如果打印的是简单类型则会列出简单类型的的类型和值,如果是对象会打印出对象的地址。 
po,print Object 的缩写,用于输出OC对象 
如下如,当运行到断点处时,控制台就会出现LLDB的调试命令行。我们只需在这里进行调试。 
这里写图片描述

expr:expression的缩写,可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。 
如上图,你在控制台输入  
expr a=2 
你就能看到 
(NSInteger) $11 = 2 
这是a的值就被动态改成了2 
除此之外,还可以使用这个命令生成一个新的对象,如: 
expr int $b = 0 
p $b 这条命令用于输出新申明对象的值(注意要加$)

image: image命令可用于寻址,有多个组合命令,在控制台输入help image可查看image的用法。比较实用的用法是用于寻找栈地址对应的代码位置,下面我们来举个例子:

NSArray *array = @[@1,@2];
NSLog(@"%@",array[2]);
  • 1
  • 2

这段代码很明显会crash,运行之后抛出下面的异常

2016-03-23 22:26:11.014 Test[3631:136626] *** Terminating app due to uncaught exception \'NSRangeException\', reason: \'*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]\'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000104f28f45 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x00000001049a2deb objc_exception_throw + 48
    2   CoreFoundation                      0x0000000104e17b14 -[__NSArrayI objectAtIndex:] + 164
    3   Test                                0x00000001044a5829 -[ViewController viewDidLoad] + 265
    4   UIKit                               0x0000000105467cc4 -[UIViewController loadViewIfRequired] + 1198
    5   UIKit                               0x0000000105468013 -[UIViewController view] + 27
    6   UIKit                               0x000000010534151c -[UIWindow addRootViewControllerViewIfPossible] + 61
    7   UIKit                               0x0000000105341c05 -[UIWindow _setHidden:forced:] + 282
    8   UIKit                               0x00000001053534a5 -[UIWindow makeKeyAndVisible] + 42
    9   UIKit                               0x00000001052cd396 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131
    10  UIKit                               0x00000001052d39c3 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1750
    11  UIKit                               0x00000001052d0ba3 -[UIApplication workspaceDidEndTransaction:] + 188
    12  FrontBoardServices                  0x0000000107c83784 -[FBSSerialQueue _performNext] + 192
    13  FrontBoardServices                  0x0000000107c83af2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
    14  CoreFoundation                      0x0000000104e55011 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    15  CoreFoundation                      0x0000000104e4af3c __CFRunLoopDoSources0 + 556
    16  CoreFoundation                      0x0000000104e4a3f3 __CFRunLoopRun + 867
    17  CoreFoundation                      0x0000000104e49e08 CFRunLoopRunSpecific + 488
    18  UIKit                               0x00000001052d04f5 -[UIApplication _run] + 402
    19  UIKit                               0x00000001052d530d UIApplicationMain + 171
    20  Test                                0x00000001044a5baf main + 111
    21  libdyld.dylib                       0x000000010764c92d start + 1
    22  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

现在我怀疑出错的地址是0x00000001044a5829(可根据执行文件名或最小的栈地址判断)为进一步精确定位我们可输入以下命令image lookup --address 0x00000001044a5829 
命令执行后返回结果如下:

Address: Test[0x0000000100001829] (Test.__TEXT.__text + 265)
Summary: Test`-[ViewController viewDidLoad] + 265 at ViewController.m:21
  • 1
  • 2

由此,我们可以看出出错的地方是ViewController.m文件的第21行。 
我们还可以使用image lookup命令查看具体的类,如下:

(lldb) image lookup --type UIView
Best match found in /Users/jamalping/Library/Developer/Xcode/DerivedData/Test-gviuudbzlyhssmanjxpwhchdbscz/Build/Products/Debug-iphonesimulator/Test.app/Test:
id = {0x00001e8d}, name = "UIView", byte-size = 8, decl = UIView.h:144, clang_type = "@interface UIView : UIResponder
@property ( getter = isUserInteractionEnabled,setter = setUserInteractionEnabled:,assign,readwrite,nonatomic ) BOOL userInteractionEnabled;
@property ( getter = tag,setter = setTag:,assign,readwrite,nonatomic ) NSInteger tag;
@property ( readonly,getter = layer,setter = <null selector>,nonatomic ) CALayer * layer;
@property ( readonly,getter = isFocused,setter = <null selector>,nonatomic ) BOOL focused;
@property ( getter = semanticContentAttribute,setter = setSemanticContentAttribute:,assign,readwrite,nonatomic ) UISemanticContentAttribute semanticContentAttribute;
@end
"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

call 
call:即调用,如我们在viewDidLoad: 设置一个断点,在程序中断的时候输入call self.view.backgroudColor = [UIColo redColor]继续运行程序,view就变成红色了,在调试的时候灵活运用call命令可以达到事半功倍的效果。

 

 

最近一直没有更新简书是因为在开发和测试阶段,有任务,没有进行学习,不过在做任务的时候也遇到了一些技术点,在这里总结一下。

      今天我们来谈一谈�Xcode调试的技巧。�就像玩游戏,有些玩家他们知道怎么操作,会放技能会走路,但是他们不知道买装备,玩了一局下来,鞋子小刀都没有买,这样行走江湖很危险啊!所以我们出门要把装配佩戴好,学会装备自己才是王道!

      本文将简书和各大博客上边涉及该方面的内容进行汇总,并试图进行全方位的总结,如果有什么不足之处还希望大家给提出来,我会进行补充和修改。现在我们开始吧。

       总体看来,关于调试的方法包括以下几个:�日志输出&LLDB、断点、性能、一些小技巧等几个大的方面。我们一一进行学习和总结。

       尝试接受新鲜事物和方法,方法都是熟能生巧的,各种方法综合运用,用好了会事半功倍。

1、�日志输出&LLDB

       关于日志输出,我们最先想到的是NSLog,但是弊端在于我们需要在想要打印的位置添加NSLog代码并重新运行项目,这样耽误时间,所以我们平时用的比较多的是打断点,然后po一下。这个“po”就是LLDB里面的一句命令。

       那么,什么是LLDB呢:它是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。我们可以简单的理解成它是一个调试器

    (1)LLDB命令行

       像下图中,我们打断点后,控制台右边里面会出现一个“lldb”,我们平时不怎么关注它,但我们一直在使用它。

 
 

      <1>  help命令

      断点的时候,我们在控制台右边lldb后边输入一个“help”,然后敲回车,就会看到所有关于lldb的命令以及各自的介绍,如下图:

 
 
 
 

      <2>  print命令

       print很好理解,就是打印,使用过程中我们可以直接用p来代替print。

       <3>  expression命令

        该命令可以改变程序实际参数的值,目的是方便了调试:不用重新运行项目。例如下图中,我们简单的做一个测试,令苹果=1,橘子=2,all应该=3,在断点过程中,我们用expression命令修改了橘子的值,令橘子=5,结果再打印all的时候,all=6(亲测好使)。使用过程中我们可以直接用 expr来代替expression。

 
 

        这里我们注意到一个“$9”,这里的9是我们使用lldb命令的次数,例如下图,我们expression一次橘子,po了一次all,再print橘子的时候,显示的是“$11”,说明我们print命令是$10。这个不用管,只是提醒我们一下而已,作用应该不是很大。

 
 

       <4>  po命令

        现在我们来看看平时用的比较多的“po”,它是“print object”的简写。po一下,我们就可以看到对象的详细信息。po使用的比较多,使用起来也比较简单、方便,这里不做多余的介绍。

        <5>  image命令

image list 查看工程中使用的库

image lookup --address  0x000000010e0979ac  程序崩溃的时候定位,查看具体报错位置

       这个其实我们可以想办法用在我们崩溃日志的收集里面,这样的话我们就可以直接定位到崩溃信息的具体位置了。(亲测,不好使。感觉xcode反馈的崩溃信息不准确。)

       <6>  call命令

        call即调用的意思。上述的po和p也有调用的功能。所以一般只在不需要显示输出,或方法无返回值的时候使用call。 和上面的命令一样,我们依然在viewDidLoad:里面设置断点,然后在程序中断的时候输入下面的命令:

call  [self.view setBackgroundColor:[UIColor redColor]]

        继续运行程序,看看view的背景颜色是不是变成红色的了!在调试的时候灵活运用call命令可以起到事半功倍的作用(亲测好使)。

(2)LLDB调试栏

 
 

      一般的按钮和功能我们用的比较多,也比较熟悉,这里我们着重介绍一下Debug View Hierachy和xcode8新增的memory graph功能。

      <1>Debug View Hierachy

 
 

       Debug View Hierachy,翻译过来就是:调试视图层次。除了点击控制台出的图标,也可以从菜单中选择Debug > View Debugging > Capture View Hierarchy 来启动视图调试。(我们可以看出xcode开发人员的用心之处:重叠在一起的长方形,我们大概就明白这个按钮是表示层级关系的)在断点或者不是断点的情况下都可以通过点击这个按钮查看视图层级关系。点击按钮,我们会在xcode最顶端的地方看到下图的一个信息:

 
 

       capture user interface for YourAppName:capture是捕获的意思,interface,face我们知道是脸,inter是进入的意思,interface就是进入脸,我们大概能够明白这句话的意思就是“为你的app捕获用户交界面”。

 
 

从左到右控件排序:(上图中也简单解释了各个功能)

调整视图间距:调整不同视图间的间距。

展示被剪切的内容:当前展示视图中被剪切的部分。

展示约束:展示选中视图的约束。

重置查看区域:将3D渲染透视图恢复至默认状态。

调整查看模式:选择性地展示3D渲染透视图,比如仅展示内容,仅展示框架以及同时展示内容和框架。

缩小:缩小3D渲染透视图

恢复:将3D渲染透视图恢复至默认尺寸。

放大:放大3D渲染透视图

调整可视视图范围:隐藏视图或展示视图,一步步解析3D渲染视图,向左或者向右滑动滑块儿有相反的效果。

       有了这个图层关系,我们可以很清楚的知道页面上边的各个控件的位置关系,因为我们在开发阶段�尤其是测试阶段,某个控件上边的字不显示,或者控件的字被遮挡,我们可以用视图调试器查看,是否控件是frame设置的不合理。

        <2>memory graph

        【经验1】

        这个是xcode8新增的功能,翻译过来的意思就是:内存图。有了内存图我们就可以解决闭包引用循环问题了。举个栗子,我们写个循环引用,如下:

 
 
 
 
 
 

     (说实在的,这几个命令我在终端不知道怎么调用,试了半天,还是没有搞出来,应该就是内存图调试的树状结构。如果有谁在终端里面知道怎么搞出来,烦请告诉我一下具体怎么操作,谢谢了!)所以,这里我们直接看这个memory graph按钮点击后的效果。

      【经验2】   

       说明1:这个功能是xcode8新增的功能,那么xcode7上边肯定找不到!而xcode8还要10.11.5以上的系统,所以,建议大家先把升级电脑系统,然后安装xcode8。

       说明2:  真机的话还需要iOS9或者10的系统。

       说明3:查找当前默认Xcode.app的developer路径---终端命令行:xcode-select -p。

                    如果安装了多个版本的xcode工具,可以使用xcode-select命令指定命令行指令使用哪个版本xcode下的developer目录下的调试工具,即修改路径:xcode-select --switch /Applications/Xcode2.app/Contents/Developer。

      踩过的坑:

      <1>本来我用的是xcode7.2,挺好用,结果在xcode7上边显然没有这个按钮,升级到xcode8吧。

      <2>同事airdrop传来的xcode8.0和xcode8.1,结果提示安装不上,需要升级电脑的系统。

      <3>升级好了系统,安装好了同事airdrop传来的xcode8.0不显示。(!准备开始抓狂  !)难道需要8.1才行?!

      <4>安装好了同事airdrop传来的xcode8.1,依旧不显示。(!!!抓狂!!!)难道是�打开xcode时候的路径不对?!

      <5>修改打开xcode的路径为xcode8.1,依旧不显示。(早已经料到是这个结果了)难道是xcode安装的太多了?!

      <6>卸载掉xcode7.0.1、7.2、7.3、8.0,只剩下一个8.1,依旧不显示。(淡定的接受这个结果)难道是同事传的xcode包有问题?!在App Store上自己下载!!

        <7>�下载好了,安装好了,依旧不显示。(�生无可恋了。。。)

        就在此时,我不知道是被逼疯了还是灵光一闪,拿了同事的真机运行项目,结果居然有了memory graph按钮!!!!

当你认为最困难的时候,恰恰也就是你最接近成功的时候!

当你放弃的时候,你永远不会知道你离成功是那么的接近!

成功很简单,就是在你坚持不住的时候,再坚持一下!

所以建议使用memory graph功能之前确保:

升级电脑系统并安装xcode8.1。(8.1比8.0更稳定)(这里其实并不非得是App Store自己下载,别人用airdrop传的也行)

真机iOS系统在9.0以上。

      【经验3】

       当你点击这个按钮以后,xcode上边的状态条会显示下图:

 
 

       Capturing memory graph for YourAppName:翻译过来就是正在捕捉你的app内存图。

       来来来,我们先来看看这狗东西......不是,是点击后的效果图