xcode debug

引言:

程序调试技巧在开发过程中起着举足轻重的地位,熟练的使用可以加快我们捕捉问题的速度. 毕竟BUG这个词是我们程序员一直要伴随的字眼,最关键的,人不是计算机,总有那么一点点小细节容易在我们慎密的思绪中偷偷溜走,从而导致一个BUG的出现.那么本文就是为了介绍关于在开发iOS程序时有哪些好用的技巧辅助我们迅速的找到错误.


参考资料:

1:Xcode的控制台调试命令

http://blog.csdn.net/likendsl/article/details/7576549



使用:

NSLog:

通过NSLog开启了我们程序的调试之旅. 不过NSLog提供的调试信息实在太少.默认只能得到打印时间和工程名称(这个基本没用.) ,就像下面这样:

[csharp]  view plain copy
  1. NSLog(@"hello world!");  

输出结果:

2013-05-30 10:01:58.704 DebugDemo[536:c07] hello world!

但在实际项目中,我们需要更多的调试信息,包括这条日志信息来自哪个函数,第几行代码等等来辅助我们梳理程序的流程.

为此,通过一些宏命令辅助,可以达到这方面的效果,代码如下:

[csharp]  view plain copy
  1. #ifdef DEBUG  
  2. #   define NSSLog(fmt, ...) {NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);}  
  3. #else  
  4. #   define NSSLog(...)  
  5. #endif  

上面的代码是一段宏命令,需要在类方法外声明,建议放在全局头文件中以供所有业务类使用. 调用方式如下:

[csharp]  view plain copy
  1. NSSLog(@"hello world!");  
输出结果:

2013-05-30 10:24:21.868 DebugDemo[782:c07] -[ViewController strongNSLog:] [Line 44] hello world!

可以看到,打印的信息提供 类名称 , 函数名称 , 代码在第几行 等非常有用的参考信息.当然这些信息的输出是需要消耗系统资源的,也就是说如果频繁的去使用函数打印日志,将很有可能导致UI界面卡住,程序运行不流畅等不利因素.不过呢,通过上面的代码我们已经很好的规避了这个问题,我们只在程序的DEBUG模式下才打印信息,如果不是DEBUG模式就以 三个点 的方式来表达代码不做任何事情,我们只需要理解这个目的就好了.


到此,我们增强了我们的NSLog,让它可以做更多的事情.

Description:

description是来自NSObject的一个类成员方法, 通常我们可能需要输出某个类实例的相关信息,就像下面这样(你的自定义类,肯定继承自NSObject吧?):

[csharp]  view plain copy
  1. NSLog(@"%@",[[TestClass alloc] init]);  

输出结果:

2013-05-30 11:08:56.671 DebugDemo[1042:c07] <TestClass: 0x8a57190>

或者是打印一个继承自UIView的类呢?

[csharp]  view plain copy
  1. NSLog(@"%@",[[UICustomView alloc] init]);  
输出结果:

2013-05-30 11:13:42.899 DebugDemo[1081:c07] <UIView: 0x71c0fd0; frame = (0 0; 0 0); layer = <CALayer: 0x71c10f0>>

观察输出结果发现这两个类打印出来的信息并不一样, 这是因为UIView本身也是继承自NSObject,但是它自己已经重写了description函数.所以结果不一样.

也就是说,一旦我们重写了description函数,我们再次对类通过 %@的方式输出信息,目标类会直接调用重写后的description函数,并输出结果.

重写代码如下:

[csharp]  view plain copy
  1. - (NSString *)description  
  2. {  
  3.     NSMutableString *mutableString = [[NSMutableString alloc] init];  
  4.     [mutableString appendFormat:@"\n frame:%@",NSStringFromCGRect(self.frame)];  
  5.     [mutableString appendFormat:@"\n color:%@",self.backgroundColor];  
  6.     return mutableString;  
  7. }  

通过重写 description 函数可以辅助我们在开发调试过程中去获取更多 自定义类 方面的信息


调试:

为了进入调试状态,我们随时需要在代码中的某一行设定一个断点,当程序执行到这行代码时,大部分运行时的进程都被强制的阻塞(计时器,网络类方面无法阻塞).

将接下来的执行权利交由给开发者.就像下面这样:


为了加快我们的操作速度,我们应该牢记调试相关的快捷键:

清空控制台: command+K

显示隐藏控制台:shift+command+Y

Continue/Pause program exeecution: control+command+Y

Step over:F6

Step into:F7

Step out:F8


当进入调试状态时,我们有几个常用的调试技巧

1:我们随时可以打印当前类的成员变量和局部变量,就像下面这样操作:


点击后,相当于执行 NSLog(@"%@",object); 会将结果立刻显示到控制台.

2:打印某个View,或者是当前方法体内的局部View的层级,直接在控制台输入如下代码:

[csharp]  view plain copy
  1. po [[[[UIApplication sharedApplication] delegate] window] recursiveDescription]  
如下图所示:






Crash:

我们在开发过程中,总是不可避免的产生你无法预期的Crash.其实拥有了ARC以后,Crash的机会相对少了很多,只不过偶尔还是要来那么几次.最怕的,就像下面这样,产生了Crash,却停留在main.m代码里:


这样的Crash提示对于我们来说没有任何帮助,当然有经验的开发者会去查看控制台自动输出的Crash信息,如下:


通过exceptionreason来定位产生Crash的主要原由.

可是在这样的情况,我们只能去猜测错误大概在哪个类,尝试着在可能出现Crash的代码上面设置一个断点,一步一步调试最终定位到真正产生Crash的那一行代码.

这样效率明显是非常低的,那有没有办法可以迅速的定位错误的具体位置呢?

有!

在我们的XCode中找到Show the Breakpoint Navigator,按照下图中来设置一个全局异常断点


当我们再次运行程序并尝试模拟刚刚产生的Crash, 结果发现,XCode准确的定位到了产生Crash的具体位置.这实在太棒了!


以上是在开发人员开发过程中遇到Crash的跟进方式.

那么交付给测试人员测试时遇到Crash呢?此时又应该怎么收集呢?

正因为有这样的需求iConsole诞生了, 详细使用请查阅我的另一篇关于iConsole介绍的博客


又那么产品正式发布了,还是遇到Crash了呢?

所以Crashlytics也诞生了,具体的使用可以参考这篇关于介绍如何使用Crashlytics的博客:



小技巧:

我们每一次编码完成后紧接着便是编译运行起来,看看程序运行的结果是否达到了我们的预期,此时,我们离不开控制台给我们输出必要的信息,为此,

当程序跑起来时,我们的控制台遍自己弹出来,这是不是蛮好的?  又当我们结束调试需要继续编码时控制台自动隐藏是不是更好? 那么,就按如下设置吧:

1:当编译运行起来以后自动显示控制台


2:当结束运行状态时自动隐藏控制台:







总结:

迅速的解决问题是一件非常愉快的事情,每当修复一个BUG时就意味着我们的程序更加健壮了. 




转载自:http://www.itivy.com/iphone/archive/2012/3/14/634673323546074649.html

认识到debug模式和断言带来的方便,我迫不及待地便将项目的schema重新设置回了debug模式

以前不懂,认为程序在发布的时候用的是release模式,为了降低发布时出现bug的几率,

便很早前就将程序设置为debug模式。

现在看来真是愚蠢之极。

不知道有多少次,我在调试bug的时候,判断空指针用了如下的代码

if(var == nil) {

NSLog(@"var==nil,fuck!");

}

浪费了多少书写时间,bug调试完毕我又得注释或者删除之。

这都是我流过的汗水,花过我的时间,证明过我曾为程序的健壮性而努力过。

删除过后,别人对我的辛劳付出一无所知。

这自然是由我不知道使用“好用的工具”所致,是一种愚蠢的行为,但这毕竟。。。

是有菜鸟进步为老鸟所必须经历的路程,不能一棒子将我打死吧?

拉回!

用了debug模式,我知道下面的方法来暴力调试更加有效率

NSAsser(var!=nil, NSLog(@"var==nil,fuck!"));

代码量不见得少了多少,或许还多了,但使用完毕之后不用再回头来擦屁股,

而且燕过有痕。。。这便是效率的提升。

而且让别人知晓,我不仅仅只是做出了功能,而且,我的功能还很健壮~


拉回,again!

ok,已经认识到debug模式和NSAssert带来的好处。

话说我已将release模式改为debug模式,原本能够正常运行的程序便卡住。

看控制台输出:

2012-03-14 14:14:11.600 GameSceneEx[26517:707] *** Assertion failure in -[CCLayer addChild:z:tag:], /Users/user/Documents/GameScene/libs/cocos2d/CCNode.m:383

2012-03-14 14:14:11.605 GameSceneEx[26517:707] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'child already added. It can't be added again'

意思很明了,有一个断言失败了。

find selected text in workspace,发现断言的相关代码为CCNode 的

-(void) addChild: (CCNode*) child z:(NSInteger)z tag:(NSInteger) aTag

{

NSAssert( child !=nil,@"Argument must be non-nil");

NSAssert( child.parent ==nil,@"child already added. It can't be added again");

if( !children_ )

[selfchildrenAlloc];

[selfinsertChild:childz:z];

child.tag = aTag;

[child setParent:self];

if(isRunning_ ) {

[child onEnter];

[childonEnterTransitionDidFinish];

}

}

也就是说,有一个节点在被添加的时候,他的parent成员变量已经不为 nil 了(说明他已经被添加过一次了)~

开始我没有暴力调,在上述方法里面下了个断点。

我的想法是在爆出错误的时候,在 By Thread 里面走回错误报出的上一级方法。

我如愿以偿,找到了错误抛出的上一级方法。

但是,很不幸的是xcode所提示的代码行数总会有一些出入,我被暗算好几次了~

这次我有了经验,没有在让我想不通的地方多做停留

(实际上有时候xcode所定位到出的bug抛出行数非常的不准确,稍作检查发现并无问题之后千万不要再多浪费时间)

很果断的便在bug可能出现的方法里面加了10数句 NSLog,直接便进入 暴力调试的环节了


暴力调虽然有点机械,显得蠢,但目前对我来说还是相当有效的。。

2012-03-14 14:14:11.322 GameSceneEx[26517:707] cocos2d: surface size: 960x640

2012-03-14 14:14:11.362 GameSceneEx[26517:707] 11

2012-03-14 14:14:11.366 GameSceneEx[26517:707] 22

2012-03-14 14:14:11.369 GameSceneEx[26517:707] 33

2012-03-14 14:14:11.378 GameSceneEx[26517:707] 44

2012-03-14 14:14:11.399 GameSceneEx[26517:707] 55

2012-03-14 14:14:11.414 GameSceneEx[26517:707] 66

2012-03-14 14:14:11.453 GameSceneEx[26517:707] 77

2012-03-14 14:14:11.456 GameSceneEx[26517:707] 88

2012-03-14 14:14:11.473 GameSceneEx[26517:707] 99

2012-03-14 14:14:11.476 GameSceneEx[26517:707] 10

2012-03-14 14:14:11.573 GameSceneEx[26517:707] 11

2012-03-14 14:14:11.595 GameSceneEx[26517:707] 12

2012-03-14 14:14:11.600 GameSceneEx[26517:707] *** Assertion failure in -[CCLayer addChild:z:tag:], /Users/user/Documents/GameScene/libs/cocos2d/CCNode.m:383

2012-03-14 14:14:11.605 GameSceneEx[26517:707] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'child already added. It can't be added again'

果不其然,很快我便定位到 bug 抛出的准确位置了,以下贴出 bug出现的那个方法:


ok,收工!




闲言少叙,直入正题。

开胃小菜--简单的断点调试

在xcode中打开一个app,在想要break的行号上单击,即可生成一个深色的箭头标识--断点。如下图,在viewDidLoad:中设置了断点。

运行app,等待。。。就可以看到xcode在断点处进入调试模式,现在让我们把视线移到xcode右下角的控制台,有木有看到(lldb)这样一行,鼠标移到此行,输入

1 po [self view]
回车,看看控制台上是不是多了一些view的信息,如下图:

po(print object)是LLDB的一个命令,其主要功能是输出objective-c中对象(objects)的信息,与之相似的另外一个命令是 p(print),其主要功能是输出原生类型(boolean、integer、float、etc)的信息。

控制台输入

p (int)[[[self view] subviews] count]
结果如下

(int) $2 = 2

注意这个使用了类型转换告知调试器应该如何处理返回值。

技巧一:运行时修改变量的值

你以前怎么验证是不是某个变量的值导致整段程序不能正常工作?修改代码中的变量的值,然后cmd+r重新启动app?现在你不需要这么做了,只需要设置一个断点,当程序在这进入调试模式后,使用expr命令即可在运行时修改变量的值。

假如有一个loginWithUsername:方法,需要两个参数:username,password。

首先设置好断点,如下图所示:

运行app,进入断点模式后,在(lldb)后输入

1 expr username = @"username"
2 expr password = @"badpassword"
控制台会返回以下信息


1 (NSString *) $0 = 0x3d3504c4 @"username"
2 (NSString *) $1 = 0x1d18ef60 @"badpassword"
现在跳出断点,执行断点之后的两条输出语句,控制台会有以下输出


1 (0x1c59aae0) A line for the breakpoint
2 (0x1c59aae0) Username and Password after: username:badpassword
看到看吧,我们在运行时修改了变量的值,事情还可以变的更简单一些,我们可以编辑断点,让它自动填充需要的修改的变量的值,并且可以选择在此断点处不进入断点模式,仅仅修改指定变量的值,然后自动执行后续代码。


右击断点选择“Edit Breakpoint...”(或者按住cmd+option,单击断点),然后如下图所示设置断点


注意选中了最后一行(“Automatically continue after evaluating”)的选择框,这就保证运行到这个断点的时,填充变量的值,然后继续运行,并不在此处断点进入调试模式。

运行app,你会得到和上述手动设置变量的值一样的输出。

接下来单击断点,使其处于禁用状态,现在箭头的颜色应该是浅蓝色的,重新运行app,你会发现username和password的值没有在运行时被改变了。

技巧二:设置断点触发条件

断点的另外一个重要作用,是可以设置触发断点生效的条件,这样我们就可以在运行时针对特定的数据进行分析,观察app是否运行在正确的轨道上。如下图:

上述截图可以看到如下语句


1 (BOOL)[(NSString*)[item valueForKey:@"ID"] isEqualToString:@"93306"]
通过这行语句,我们告诉编译器:当item中ID等于93306时,此断点生效,进入断点调试模式。


技巧三:格式化输出数据

如果你厌倦了代码里无穷无尽的NSLog,幸运的是我们可以在编辑断点使其输出格式化字符串就像平常编码时一样。不过有一点需要注意,平常编码时可能会使用NSString‘s stringWithFormat:输出格式化字符串,不过这个方法貌似在断点中木有效果,你需要使用alloc/init形式的方法,如下:

1 po [[NSString alloc] initWithFormat:@"Item index is: %d", index]

运行app,就能在控制台看到想要的输出啦!

简单!强大!这就是LLDB给你的选择,从此代码里可以不用再有NSLog满天飞的情况了,代码变得更干净了,心情变得更愉悦了!

LLDB还有很多强大的地方,本教程只不过揭开了它的面纱,即便如此,仍让我们沉醉不已。

如此你有让xcode中debug变的更轻松加简单的方法,请在评论中尽情的分享!


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值