Objective-C的内存管理实质上就是引用计数
有两种形式,以前是MRC,现在是ARC。
MRC:Manual Reference Counting(手动内存管理),对引用计数器的操作完全由程序员完成
ARC:Automatic Reference Couting(自动内存管理),由编译器进行内存管理。现在编译器默认ARC为有效状态
首先,介绍一下内存管理的思考方式(在ARC无效时)
1.自己生成的对象自己持有:使用alloc、new、copy、mutableCopy方法生成的对象自己持有。
2.非自己生成的对象自己也能持有:使用retain方法持有非自己生成的对象。
3.不在需要自己持有的对象时释放:使用release方法释放对于对象的持有。
4.非自己持有的对象不能释放:释放非自己持有的对象程序会崩溃。造成崩溃有两种情况:再次废弃已经废弃的对象,访问已经废弃的对象;释放非自己持有的对象
既然说内存管理就是引用计数,那引用计数是怎么计数的呢?
生成对象并持有(alloc / new / copy / mutableCopy方法)引用计数加1,持有对象(retain方法)引用计数加1,释放对象(release方法)引用计数减1,当对象的引用计数为0时表示没有持有者,那么该对象会被废弃(dealloc方法)。
下面请看代码(以下代码在ARC无效时可行)
/*
自己生成的对象自己持有
*/
id obj = [[NSObject alloc]init];
NSLog(@"count:%lu",(unsigned long)[obj retainCount]);
/*
obj持有对象,引用计数为1
*/
/*非自己生成的对象也能持有*/
id obj1 = [obj retain];
NSLog(@"after obj1 retain---count:%lu",(unsigned long)[obj retainCount]);
/*
obj1持有对象,引用计数加1,此时引用计数为2
*/
/*
不再需要自己持有的对象时释放
*/
[obj1 release];
NSLog(@"after obj1 release---count:%lu",(unsigned long)[obj retainCount]);
/*
obj1释放对对象的持有,引用计数减1,此时引用计数为1
*/
/*
无法释放非自己持有的对象
*/
//[obj1 release];
/*
程序崩溃:再次释放已经非自己持有的对象时崩溃;
NSObject object 0x1004043f0 overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug
*/
关于alloc、retain、release、dealloc的实现,也就是引用计数的实现。
苹果采用散列表(引用计数表)来管理引用计数,将引用计数保存在引用计数表的记录中。优点:对象用内存块的分配不用考虑内存块的头部;引用计数表存有内存块地址,即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有被破坏,就能够确认各个内存块的位置
GNUstep(cocoa框架的互换框架,虽然与苹果的cocoa实现不能完全相同,但是两者的实现方式一样)采用将引用计数保存在内存块头部的变量中来实现对引用计数的管理。优点:代码少;能够统一管理引用计数用内存块与对象用内存块
当ARC无效时,还有一个重要的方法不得不提啦~
autorelease方法,顾名思义就是自动释放,看起来像是ARC,但只能在MRC下使用。该方法可以取得对象的存在,但不持有该对象,当对象超出指定的生存周期时能够自动并正确的释放对象(调用release方法)。它本质上是调用NSAutoreleasePool对象的addObject类方法()。
具体使用方法为:生成并持有NSAutoreleasePool对象;调用已分配对象的autorelease实例方法;废弃NSAutorelPool对象。
对于所有调用autorelease方法的对象,当NSAutoreleasePool对象废弃时,都将调用release实例方法。但是只要NSAutoreleasePool对象不被废弃,那么这些对象也不会被废弃,故当产生大量autorelease的对象时,可能会产生内存不足的现象。当几个NSAutoreleasePool嵌套时,使用最内侧的对象。
这一块有一个问题需要注意,如果NSAutoreleasePool对象调用autorelease方法会怎样呢?
答:会发生异常。这是因为无论哪种变量调用autorelease实例方法实质上都是调用NSObject类的autorelease实例方法。对于NSAutoreleasePool类,autorelease实例方法已被该类覆盖,故运行时会出错。
下面请看代码(ARC无效时可行)~~~
/*
自己生成的对象自己持有
*/
id obj = [[NSObject alloc]init];
NSLog(@"count:%lu",(unsigned long)[obj retainCount]);
/*
obj持有对象,引用计数为1
*/
/*
autorelease方法取得对象但是不持有对象,
*/
id obj2 = nil;
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];
obj2 = [obj autorelease];
NSLog(@"after obj2 autorelease---count:%lu",(unsigned long)[obj retainCount]);
NSLog(@"obj2:%@",obj2);
[pool drain];
//NSLog(@"after pool drain obj2:%@",obj2);
//运行时在此处程序崩溃,因为NSAutoreleasePool对象被释放obj2也随之被释放,所以访问一个已经被释放的对象程序崩溃
NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc]init];
[pool1 autorelease];// 崩溃原因:-[NSAutoreleasePool autorelease]: Cannot autorelease an autorelease pool'
总结:在ARC无效时,通过调用retain / release / autorelease 实例方法来管理内存,完成引用计数。