Apple在iOS5之后引入了自动ARC技术,让编译器来自动来管理内存,开发者从此不用再去写很多retain、release代码了。ARC下,编译器完全清楚目标对象,并且能够自动释放那些需要释放的对象,因此,程序内存泄漏的风险大大降低了,但!ARC!不!是!万!能!的!
在实际开发中,内存泄漏还是比较常见的,比如使用block时候的不注意,导致的循环引用;比如在使用NSTimer实例的时候,timer的action事件执行者也就是Target会强引用Self,如果timer没有正确的销毁,就会造成内存泄漏等等;这个时候我们就需要来手动管理内存了。因此,很有必要知道Objective -C的内存管理机制。
二、Objective-C内存管理是机制,就是引用计数。
正确客观的思考方式:
1)自己生成的对象,自己持有。
2)非自己生成的对象,自己也能持有。
3)自己持有的对象,一旦不再需要,就需要立即释放。
4)非自己持有的对象,自己无法释放。
这里的“自己”,知道是当前的对象的使用环境,也可以理解为程序猿自己啦。
问题一:
什么情况下自己生成的对象,自己持有?
使用以下名称开头的方法名,意味着自己生成的对象只有自己持有:
a)alloc
// 自己生成并持有
id obj = [[NSObject alloc] init];
使用类的alloc方法就能生成一个对象,在这里,指向生成并持有的对象的指针被赋值给变量obj。
b ) new
// 自己生成并持有对象
id obj2 = [NSObject new];
在Objective-C中,[[NSObject alloc] init]和[NSObject new]是完全一致的。
c)copy
d )mutableCopy
copy和mutableCopy都是生成并持有对象的一份副本,虽然持有的是副本,但是,副本是自己生成的。
另外,除以上的几个,以下名称开头的方法也意味着自己生成并持有。
(1)allocMyObject
(2)newThatObject
(3)copyThis
(4)mutableCopyYourObject
但是对于一下名称,即使用alloc/new/copy/mutableCopy名称开头,并不属于同一类方法。
(1)allocate
(2)newer
(3)copying
(4)mutableCopy
问题二:
什么情况下自己非自己生成的对象,自己也能持有?
用alloc/new/copy/mutableCopy等开头以外的方法取得的对象,因为不是自己生成的对象,所以并不持有这个对象。比如一些类方法。
// 取得非自己生成的对象,自己并不持有
id array = [NSMutableArray array];
// 如果要持有对象,那么就向对象发送retain消息
[array retain];
取到一个非自己生成的对象,但是自己可以持有对象的实现如下,向对象发送autorelease消息,告诉系统这个对象需要被引用,延缓释放:
- (id) object{
id obj = [[NSObject alloc] init];
[obj autorelease]; //obj 放入到NSAutoreleasePool中,延迟释放。
//取得的对象存在,但是自己不持有对象
return obj;
}
其实,由类方法创建的对象都会放入到NSAutoreleasePool中。后面会继续讲。
问题三:
如何释放自己持有的对象?
向自己持有的对象发送release消息。
// 自己生成并持有
NSArray *arr1 = @[@"11",@"22"];
// 释放自己生成且持有的对象
[arr1 release];
// 取得非自己生成的对象,自己并不持有
id array = [NSMutableArray array];
// 如果要持有对象,那么就向对象发送retain消息
[array retain];
// 释放自己持有的对象
[array release];
这里有个需要弄明白的:
1)释放 !== 销毁
一个对象可以有多个持有者,且持有者只能向自己持有的对象发送release消息,由引用计数器原则,当一个对象的retainCount == 0,这个对象才会被销毁。多个持有者时,每次release都会使对象的retainCount - 1。
实例如下代码:
// 创建并持有一个对象。
id obj = [NSObject new];
NSLog(@" create new obj === %lu ===",[obj retainCount]);//retainCount=1
NSMutableArray *mutableArray = [NSMutableArray array];
// 数组对象持有 对象 obj
[mutableArray addObject:obj];
NSLog(@" mutableArray have obj === %lu ===",[obj retainCount]); //retainCount=2
id dict = [NSMutableDictionary dictionary];
// 字典对象持有 NSObject对象 obj
[dict setObject:obj forKey:@"oc"];
NSLog(@" dict have obj === %lu ===",[obj retainCount]);//retainCount=3
// 字典对象释放
[dict release];
NSLog(@" dic release === %lu ===",[obj retainCount]);//retainCount=2
// 数组对象释放
[mutableArray release];
NSLog(@" mutableArray release === %lu ===",[obj retainCount]);//retainCount=1
// obj对象释放
[obj release];
NSLog(@" obj release === %lu ===",[obj retainCount]);//retainCount=1
所以,释放跟销毁不是一个概念,它们并不等价。
2)赋值 !== 持有
下面这句代码,NSMutableArray类对象被赋给变量obj,但变量obj自己并不持有改对象。所以,赋值并不等价于就是持有。
id array = [NSMutableArray array];
问题四:
对于“非自己持有的对象,自己无法释放“这句话的解释。
其实就是被释放或被销毁的对象不能重复被引用。
// 创建并持有一个对象。
id obj = [NSObject new];
NSLog(@" create new obj === %lu ===",[obj retainCount]);
// obj对象释放
[obj release];
NSLog(@" obj release === %lu ===",[obj retainCount]);
// obj对象释放
[obj release];
NSLog(@" obj release === %lu ===",[obj retainCount]);
第二次释放的时候程序会崩溃。
二、alloc/retain/release/dealloc
关于alloc/retain/release/dealloc的实现可参看GNUstep,亦可以查阅 《Objective - C高级编程》这本书上的讲解。这里就不赘述了。
三、引用计数的实现
这一块儿理解的不是很好,大致的是这么回事儿:alloc类方法首先调用allocWithZone:类方法,然后调用create_Instance函数,然后通过调用calloc方法来分配内存。Apple内部使用一个引用计数表来管理所有对象的引用计数,同时,引用计数表还记录着每个对象的内存块地址。每次调用release方法都会去技术表中查询对象的引用计数的值。
具体总结如下:
(1)在Objective-C这一对象中存在引用计数器这一整数值。
(2)调用alloc 或者retain方法后,引用计数值会加1.
(3)调用release后,引用计数值减1.
(4)引用计数值为0时,调用dealloc方法废弃对象。