我们知道,在Objective-C消息和转发机制的背后有这样一个说法:调用没有实现或者不存在的方法,会导致App崩溃,但是如果你比较细心或者专门研究过消息和转发机制那么你应该听过这样的结论:Objective-C中调用nil的任何方法都不会崩溃,但是,为什么就不会崩溃呢?这里就简单分析一下具体的原因和底层的实现方式……
访问了一个已经被释放的对象
我们知道在不使用 ARC 的时候,内存要自己管理,这时重复或过早释放都有可能导致 Crash。
NSObject * aObj = [[NSObject alloc] init];
[aObj release];
NSLog(@"%@", aObj);
原因
aObj 这个对象已经被释放,但是指针没有置空,这时访问这个指针指向的内存就会 Crash。
解决办法
使用前要判断非空,释放后要置空。正确的释放应该是:
[aObj release];
aObj = nil;
由于ObjC的特性,调用 nil 指针的任何方法相当于无作用,所以即使有人在使用这个指针时没有判断至少还不会挂掉。
那么这里就有一个问题?
为什么调用nil的任何方法都不会崩溃呢?
首先在Objective-C里,nil对象被设计来跟NULL空指针关联的。他们的区别就是nil是一个对象,而NULL只是一个值。而且我们对于nil调用方法,不会产生crash或者抛出异常。这个技术被framework通过多种不同的方式使用。
最主要的就是我们现在在调用方法之前根本无须去检查这个对象是否是nil。假如我们调了nil对象的一个有返回值的方法,那么我们会得到一个nil返回值。
我们先来看看这断代码:
- (void) dealloc
{
self.caption = nil;
self.photographer = nil;
[super dealloc];
}
具体原因
之所以可以这么做是因为我们给把nil对象设给了一个成员变量,setter就会retain nil对象(当然了这个时候nil对象啥事情也不会做)然后release旧的对象。这个方式来释放对象其实更好,因为这样做的话,成员变量连指向随机数据的机会都没有,而通过别的方式,出现指向随机数据的情形机会不可避免。
注意到我们调用的self.VAR这样的语法,这表示我们正在用setter,而且不会引起任何内存问题。假如我们直接去设值的话,就会有内存溢出:
// incorrect. causes a memory leak.
// use self.caption to Go through setter
caption = nil;
这里进入提到了崩溃,那么就大概整理一下开发中常见的崩溃问题和类型,方便提前预防和部分相关处理
一、访问了一个已经被释放的对象:nil,autorelease
二、访问数组类对象越界或插入了空对象:分类或者runtime替换
三、访问了不存在的方法:判断是否有,runtime修改
四、字节对齐:使用 memcpy 来作内存拷贝,而不是直接对指针赋值
五、堆栈溢出(过多的递归会导致栈溢出,过多的 alloc 变量会导致堆溢出。)
六、多线程并发操作:加锁 ,原子,Operation Objects, GCD, Idle-time notifications, Asynchronous functions, Timers, Separate processes。
七、Repeating NSTimer:写了个宏用来释放Timer