解决EXC_BAD_ACCESS错误的一种方法--NSZombieEnabled

时间:2023-01-14 19:29:06

iOS 程序开发时经常用遇到 EXC_BAD_ACCESS 错误导致 Crash,出现这种错误时一般 Xcode 不会给我们太多的信息来定位错误来源,只是在应用 Delegate 上留下像Thread 1: Program received signal:"EXC_BAD_ACCESS",让问题无从找起。

比如你对已释放的对象发送消息时就会出现,EXC_BAD_ACCESS

再如release 的对象再 release,release 那些autorelease 的对象等也会报这样的错。默认设置下 Xcode 不会给你定位具体是哪一行代码,不该去使用已释放的对象,或者release 用错了。

比如 UIViewController 子类中这样的代码:

  1. static NSMutableArray*array;
  2. -(void)viewDidLoad
  3. {
  4. [superviewDidLoad];
  5. array= [[NSMutableArray alloc]initWithCapacity:5];
  6. [array release];//释放掉该数组
  7. }
  8. - (void)viewWillAppear:(BOOL)animated{
  9. [array addObject:@"Hello"];//使用释放掉的数组
  10. }

上面的代码就会出现EXC_BAD_ACCESS 错误,但我执行时 Xcode 一出错却是定位在我在 AppDelegate 的 application:didFinishLaunchingWithOptions: 方法上的某行了,如果代码量多了,要查找具体问题非常难,但凭经验了。

不过NSZombieEnabled 环境变量可以帮我们的忙,就是当设置NSZombieEnabled环境变量后,一个对象销毁时会被转化为_NSZombie,设置NSZombieEnabled后,当你向一个已经释放的对象发送消息,这个对象就不会向之前那样Crash或者产生一个难以理解的行为,而是放出一个错误消息,然后以一种可预测的可以产生debug断点的方式消失, 因此我们就可以找到具体或者大概是哪个对象被错误的释放了。

对 Xcode 设置了NSZombieEnabled 之后,Xcode 会明确定位在行[array addObject:@"Hello"],然后控制台下报的错误信息是:

*** -[__NSArray addObject:]:message sent to deallocated instance 0x6557370

如何设置 NSZombieEnabled 呢,在 Xcode3 和 Xcode4 下设置不一样,Xcode4 下设置很简单。
Xcode3 下 NSZombieEnabled 设置方法如下:

1.   在XCode左边那个Groups& Files栏中找到Executables,双击其中的一项,或者右键Get Info;
2.  切换到Arguments 
3.  这里一共有两个框,在下面那个Variables to be set in theenvironment:点+号添加一项,Name里填NSZombieEnabled,Value填Yes,要保证前面的钩是选中的。

Xcode4 下设置 NSZombieEnabled 的方法:

你可以点击 Xcode4 菜单 Product -> Edit Scheme-> Arguments, 然后将点击”加号”, 将 NSZombieEnabled 参数加到Environment Variables 窗口中, 后面的数值写上 ”YES”.

或者在 Xcode4 菜单 Product -> EditScheme -> Diagnostics 设置窗口中直接勾上Enable ZombieObjects 即可,Xcode 可用 cmd+shift+< 进到这个窗口。

解决EXC_BAD_ACCESS错误的一种方法--NSZombieEnabled

Xcode4 已经考虑到了现在的要求,所以提供了更便捷的设置的方式,你也可以在这个窗口中设置其他一些参数,你肯定能由此获得更多的帮助信息。

另外再说一下,如果没有为 Xcode 设置 NSZombieEnable,像下面的代码或许可以正确执行,打印出你所期望的结果“Hello”

===================

iOS5之后苹果公司引入了ARC机制,大大方便了ios开发者对内存的管理机制。在iphone 4出世的时候为什么ios在512M的内存中可以运行很大的游戏,保持畅快流畅的状态。得益于ios非常好的内存处理机制。

在我们现在创建项目的时候,默认会直接引入ARC机制,我们可以关闭ARC机制:在输入框中输入long点击搜索按钮,如图:解决EXC_BAD_ACCESS错误的一种方法--NSZombieEnabled

接下来即可进行老版本的内存操作了。

在老版本中,内存操作采用了引用计数(retainCount)alloc retain(+1)release(-1)

内存管理原则(配对原则):只要出现了new,alloc,retain,就一定配对出现一个release,autorelease

使引入数+1,必须对应的-1,务必成对存在

ClassA *obj1 = [[ClassA alloc] init]; //retaincount = 1

ClassA *obj2 = obj1; //retaincount = 1

[obj2 retain]; //retaincount = 2

当引入计数变为0的时候,会自动调用系统的dealloc系统函数

-(void)dealloc

{

[super dealloc];//注意一定要调用父类函数

NSLog(@"对象被删除");

}

野指针:

在开发中经常会遇到野指针,系统一般会提示为Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)错误。因为你访问了一块已经不属于你的内存。

但我们在开发中可能会发现系统并不会时时检测野指针,主要是为了提高编译效率,默认关闭了野指针检测机制,打开方法:解决EXC_BAD_ACCESS错误的一种方法--NSZombieEnabled解决EXC_BAD_ACCESS错误的一种方法--NSZombieEnabled

在Enable Zombie Objects上点击对号,关闭即可。

注意:一般我们最好不要打开该机制,这样编译时的效率将大大降低。

内存泄露:

只要对象的retainCount !=0就会一直存在在内存中,不再被使用的对象,一直在内存中没有被销毁,则会造成内存泄露。

@property 参数

1.与set方法内存管理相关参数

retain:要生成符合内存管理原则的set方法(应用与对象类型)

assign:直接赋值(对象类型,基本数据类型)

capy

2多线程相关

nonatomic:不生成多线程代码。(一般都用这个,效率高)

atomic:生成多线程管理代码

3.是否要生成set跟get方法

readwrite:可读可写属性,同时生成set跟get方法

readonly:只读属性,只生成get方法

4.set与get方法名称相关的参数

setter:设置生成set方法的名称

getter:设置生成的get方法名称

实例:

@property(nonatomic,retain)Class *class;

@property(nonatomic,retain)NSString *name;

@property(nonatomic,assign)int age;

@property(nonatomic,assign,readonly)int age;

@property(nonatomic,assign,setter=abc:)int age;//注意有冒号

  1. static NSMutableArray*array;
  2. -(void)viewDidLoad
  3. {
  4. [super viewDidLoad];
  5. array= [[NSMutableArray alloc]initWithCapacity:5];
  6. [array release];
  7. [array addObject:@"Hello"];//之所以不会crash,是在于事件周期未完,内存回收机制还没有执行,没有真正的回收掉array的对象内存。
  8. NSLog(@"%@",[array objectAtIndex:0]);
  9. }

但是一旦加上了NSZombieEnable 设置,上面的代码行  [array addObject:@"Hello"] 也将无法投机取巧了,同样会得到错误提示:

*** -[__NSArrayM addObject:]:message sent to deallocated instance 0x6557370

即使该array 所指向的内存还是原来的数据也不能逃脱掉 NSZombieEnable 的法眼。也就是之所以未设置 NSZombieEnable 时上面代码能得到正确结果,是因为,虽然 [array release] 是标记为释放掉该内存块,但是后面使用 array 时,因为该指针指向的内存数据未被覆盖,所以未出错,这和C++ 的指针 delete 后的效果是一样的。

  1. CocoaDev,个人觉得讲Cocoa技术十分专业的网站之一,下面的链接详细讲了讲NSZombieEnable的原理。http://www.cocoadev.com/index.pl?NSZombieEnabled
  2. 苹果官方的Mac OS X Debugging Magic,详细讲述了最为一个高级苹果程序员应该具备的调试技巧 http://developer.apple.com/library/mac/#technotes/tn2004/tn2124.html
  3. 其实还可以在Instruments中开启NSZombie选项,这样就可以在Instruments中直接查看crash时候的callstack了:http://www.markj.net/iphone-memory-debug-nszombie/

最后提醒NSZombieEnabled只能在调试的时候使用,千万不要忘记在产品发布的时候去掉,因为NSZombieEnabled不会真正去释放dealloc对象的内存,一直开启后果可想而知,自重!