iOS调试——基础(二)

//================================

//日志和断言输出

在程序运行过程中,可以输出一些信息到输出窗口,其中有些信息是你应用遇到错误或异常时由系统抛出的错误堆栈信息。当然,也可以根据需要自己输出或抛出异常。根据这些信息,我们可以分析程序出了什么问题以及程序的运行情况。

 


使用NSLog函数

1.NSLog函数是Foundation框架提供的OC日志输出函数,与标准C中的printf函数类似,可以格式化输出。对于不同的数据类型,NSLog函数中格式化字符串是不同的。

 


使用NSAssert(断言)

1.NSLog函数是无条件输出,即程序运行到该语句,就会输出结果。如果想要有条件输出结果,可以使用NSAssert宏。注意,NSAssert并不是函数,它的定义如下:

        NSAssert(<#condition#>, <#desc, ...#>)

其中第一个参数condition是布尔表达式,第二个参数desc是描述信息,参数 后面的....是格式化desc描述信息的。如果condition为NO,则输出desc的描述信息,并抛出异常NSInternalInconsistencyException(内部不一致异常),如果condition为YES,则不输出信息。

eg:

    for (int i = 0; i < 10; i ++) {

        NSAssert1(i >= 0 && i < 9@"i = %i变量超出了范围", i);

        int b = i *i;

        NSLog(@"b = %i",b);

    }

当变量i等于9时,条件(i >= 0 && i < 9)为NO,NSAssert宏会抛出异常,输出窗口输出的内容如下:

输出地信息有两条:

一条是输出NSAssert宏的位置所在的行

一条是异常堆栈信息。



和NSAssert类似的宏还有NSAssert1、 NSAssert2、NSAssert3、NSAssert4和NSAssert5,其中宏后面的数字代表格式化描述信息参数的个数。




移除NSLog和NSAssert

1.我们使用NSLog和NSAssert的目的是为了调试,并在调试阶段输出一些信息,但是在调试结束、应用发布后,如果还使用NSLog和NSAssert输出信息,那样会影响性能。事实上,这个工作量是比较大的,而且刚刚被移除掉的时候,会发现又要进行调试,然后再把NSLog和NSAssert加入到程序代码中,很麻烦!因此,需要设定两套不同的编译参数环境,我们把这个环境称为Scheme(方案)。

2.Xcode中的 Scheme是一些Target的集合,它们配置不同的编译参数,也可能包括了一些可运行的测试集合。Xcode的Scheme位于Xcode的左上角,如图所示:


可以编辑、新建、管理Scheme。

3.选择New Scheme菜单项,接着会弹出一个对话框,从中选择Target为Demoa,如果所示:


然后点击OK按钮就创建成功了,这样刚创建好的Scheme为Demoa3。

此时在选择Edit Scheme菜单项编辑Demoa3。如图所示:

选择Info标签,将Build Configuration(编译配置)修改为Release(发布)。

Debug:为调试编译而配置的。

Release:为发布编译配置的。

 

4.移除NSAssert

移除NSAssert比较简单,需要在TARTGETS中选择Build Settings,找到Preprocessor Macros(预处理宏)项目,配置它的Release为NS_BLOCK_ASSERTIONS.如图:



NS_BLOCK_ASSERTIONS是Foundation框架中定义好的预处理宏,如果在编译环境中设置NS_BLOCK_ASSERTIONS,在编译的时候NSAssert宏将被移除,我们可以分别运行Scheme中的Demoa->Demoa2和Demoa->Demoa3,比较测试结果,会发现Demoa->Demoa2会抛出异常,而Demoa->Demoa3可以执行通过。


5.移除NSLog

移除NSLog要比移除NSAssert复杂一些,需要修改程序代码。思路是重新定义一个宏替代NSLog,这个宏是有条件编译的。为了能在工程所有源代码中使用这个宏,需要在<工程名>-Prefix.pch文件中定义这个宏。这个文件引入的.h文件和定义的宏作用于全部工程中的源代码模块,这样可以省去在每个.h文件中定义宏。打开这个pch文件,添加定义新的日志宏,内容如下:

#ifdef DEBUG

#   define DLog(...)    NSLog(__VA_ARGS__)

#else

#   define DLog(...)

#endif

编译器在编译的时候判断是否定义DEBUG宏,如果定义了,则使用DLog替代NSLog。

NSLog(__VA_ARGS__)中的参数__VA_ARGS__也是一个宏,它是一个可以提供可变参数的宏。

于移除NSAssert雷神,需要在TARGETS中选择Build Settings,找到scheme,配置它的Debug为DEBUG或(DEBUG=1),如图所示:


 

 

NSLog输出

1.在调试过程中,经常通过NSLog在控制台输出需要的信息。NSLog输出比较消耗系统资源,输出的数据也可能会暴露出App里的保密信息,所以在发布正式版本之前必须报所有的NSLog输出都屏蔽掉。

2.NSLog除了输出基本消息后,对于结构体也可以一次性输出。



动态输出

1.在程序运行的过程中,除了通过断点调试在Xcode下方查看变量的值,也可以通过NSLog在控制台输出想要的信息。使用NSLog输出需要在运行前就把要输出的信息写好,如果有改变,需要重新编译运行,效率非常低。

2.介绍一种简单实用的方式:在程序运行的过程中,通过在控制台输入相应的指令,可以快速的打印输出需要的信息,非常的方便。

3.动态输出的指令有两个,p和po,与NSLog用法类似,p用于打印输出普通消息,po用于打印输出对象消息。使用动态指令需要与断点配合使用,这样就能动态的打印输出程序执行到某个断点时的消息。




   

 




//================================

//LLDB

dSYM文件(调试信息文件)

1.dSYM中存储着与目标有关的调试信息。

2.调试器通常会集成在开发环境中。开发环境通常支持防止断点使应用停止运行,从而查看代码中变量的值。也就是说,调试器能够实时的使应用停止运行,这样你就可以查看变量和寄存器。有两类重要的调试器:符号调试器和机器语言调试器。

    机器语言调试器:能够运行到断点时显示逆向过来的汇编代码,允许你观察寄存器的值和内存地址。汇编程序员通常使用这种调试器。

    符号调试器:能够在调试代码时显示应用使用的符号和变量。和机器语言调试器不同,符号调试器允许你观察代码中的符号,而不是寄存器和内存地址。

3.让符号调试器工作起来,需要一个编译过的代码和你编写的源代码之间的链接和映射。这正是调试信息文件中所包含的内容。调试器使用这个调试信息文件将编译过的代码——不管是中间代码还是机器码——映射回源代码。可以将调试信息文件当做游客浏览陌生城市时参考的地图。调试器能够参考调试信息文件,根据你在源代码中放置的断点让应用停在正确的位置。

4.Xcode的调试信息文件称做dSYM文件(因为文件的扩展名为.dSYM).

5.创建新工程时,默认设置是自动创建一个调试文件。如图所示:


dSYM文件会在每次构建工程时自动创建,还可以使用命令行工具dsymutil创建dSYM文件。

 


符号化

1.包括LLVM在内的编译器都是用来将源代码转换成汇编代码的。所有汇编代码都有一个基地址,你定义的变量、用到的栈和堆都会依赖这个基地址。每次运行应用时,这个基地址都会改变,尤其是在iOS 4.3以及以上版本的操作系统中,这些操作系统都采用了地址空间布局随机化(Address Space Layout Randomization)机制。

2.符号化:是用方法名和变量名(统称为符号)来替换基地址的过程。 基地址是应用的入口地址,通常就是main方法,除非你是在写一个静态库。可以符号化其他符号,方法是计算它们相对于基地址的偏移,然后将它们映射到dSYM文件中。不用担心,符号化过程在Xcode调试应用时才会进行,或者是在用Instruments做性能技术分析时进行。

 

 

1.简单的来讲就是一个调试引擎,程序运行后可以在Xcode控制台中查看lldb输出。

 

2.一些lldb命令:

help:打印帮助

print anInt:打印最基本的内容

po anObj:打印对象

expr 5+2:打印表达式

help frame:打印线程中的一些东西

 

3.LLDB在Xcode4.3或者之后的版本里面是默认的调试器,老版本中GDB是默认的调试器。

 

//================================

//异常堆栈报告分析

1.在使用Xcode工具的开发过程中,面对运行异常,很多初学者往往毫无头绪,不知道如何跟踪异常堆栈、如何分析异常堆栈报告。


跟踪异常堆栈

1.默认情况下,使用Xcode工具进行开发时,产生异常时会有信息输出,也会有异常堆栈输出,不过它们都是晦涩难懂的内存地址。

如下所示:


这些日志由系统输出,共两条日志,第一条是异常描述信息,表示不能识别

2.第二条是异常堆栈信息,除了第一行外,其他的都是十六进制数据,根本不知道什么意思,异常堆栈信息对于我们基本上没有用。

3.将十六进制数据翻译成我们能够理解的异常堆栈信息的过程叫做symbolicate(符号化).Xcode提供了symbolicatecrash工具来符号化,但使用起来比较麻烦。一般情况下,我们采用在程序中添加一小段代码实现符号化,修改一下main.m中的代码:

int main(int argc, char * argv[]) {

    @try

    {

        @autoreleasepool {

            return UIApplicationMain(argc, argv, nilNSStringFromClass([AppDelegateclass]));

        }

    }

    @catch(NSException *exception)

    {

        NSLog(@"Stack Trace:%@",[exception callStackSymbols]);

    }

}

因为main.m是程序的逻辑入口点,事实上,所有的异常最后都要抛到这里,就会由系统采用默认的处理方式。我们在进行异常捕获,然后处理异常堆栈信息,其中exception是异常对象,callStackSymbols方法可以处理异常堆栈。再次运行抛出异常,输出窗口的代码如下:


这就是异常堆栈的信息了,这个内容比系统默认的信息要容易理解的多。

 

 


分析堆栈报告

1.一条堆栈信息由5部分构成,如图所示:

 

34    PresenttationLayer    0x00001c45    start    +53

(1)    (2)                    (3)            (4)    (5)

第(1)部分:堆栈输出序号,序号越大表示越早被调用

第(2)部分:调用方法(或函数)所属的框架(或库),上面的PresenttationLayer是我们自己编写的表示层工程。

第(3)部分:调用方法(或函数)的内存地址,这个信息对我们帮助不是很大。

第(4)部分:调用方法(或函数)名,这个信息对我们很重要。

第(5)部分:调用方法(或函数)编译之后的代码偏移量,这个信息很多人误认为是行号,对我们基本没有帮助。

2.此外,堆栈信息是要从下往上看的,程序运行的过程是从下面的方法(或函数)调用项目的方法(或函数).例如:

45  UIKit                               0x0000000106d34900 UIApplicationMain + 1282

46  Part-Time-Company                   0x0000000104dbfe1e main + 142

3.在程序运行的过程中,堆栈信息可能很长,我们不需要每一行都去看,只需关注我们自己的工程(或库)就可以了。这是因为首先我们要假定别人提供给我们的框架(或库)是正确的,先看自己的工程(或库)中的方法(或函数),找到那条调用语句看看是不是有问题。


 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值