Objective-C提供三种内存管理模型:
- 自动垃圾回收
- 手动引用计数MRC和自动释放池.
- 自动引用计数ARC.
Objective-C 2.0 是支持自动垃圾回收机制的.但是iOS运行环境并不支持自动垃圾回收.而且自OS X 10.8及以后也已经不推荐使用了,而是建议使用ARC.
在iOS5之前使用的是手动引用计数简称MRC.iOS5苹果推出了自动引用计数ARC,并且推荐大家使用自动引用计数进行内存管理.自动引用计数ARC就是让编译器来进行内存管理,编译器会在合适的地方帮你插入retain或release,因此不再需要手工输入retain和release代码了.(编译时)
引用计数式内存管理的思想是:
- 自己生成的对象,自己持有. 对应 alloc/new/copy/mutableCopy方法.
- 非自己生成的对象,自己也能持有. 通过调用retain方法,就能使自己持有.
- 不需要自己持有对象时,需要释放. 通过调用release方法,释放自己持有的对象.
- 不能释放非自己持有的对象.
在这些思想的指导下,Cocoa 建立了一套明确的内存管理的方法命名规则,在编写用于对象生成或持有的方法时,必须要遵守这些命名规则.以 alloc/new/copy/mutableCopy开头的方法名意味着自己生成并持有对象.在ARC有效时,还要加一条命名规则:以init开头的方法.注意以init开头的方法的规则要比 alloc/new/copy/mutableCopy更严格.该方法必须是实例方法并且必须要返回对象.返回的对象应为id类型或instanceType.该返回的对象并不注册到自动释放池里去.基本上只是对alloc方法返回的对象进行初始化处理并返回该对象.
使用上述方法之外的方法取得的对象是自己不持有的对象,但该对象存在.(根据第四条:不能释放非自己持有的对象.此时如果是MRC环境,就不能调用release方法进行释放,否则崩溃.这种情况在MRC时经常发生.比如UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
从该方法的命名可以看出,取得的对象是自己不持有的,所以如果你对[btn release]将导致奔溃.还有一种情况就是过度release.比如UIButton *btn = [[UIButton alloc] init]; [btn release]; [btn release];
btn第一次release时,btn引用的对象就已经被释放了,btn这个指针变量已经变成一个野指针了,如果再release,即是访问这个野指针,这将导致崩溃.)
- (id)allocObject
{
id obj = [[NSSObject alloc] init];
return obj;
}
id obj1 = [obj0 allocObject]; //自己生成的对象自己持有
...
[obj1 release]; //所以这里使用完了,需要释放.
以 alloc/new/copy/mutableCopy开头的方法名意味着自己生成并持有对象.因此每次使用都需要时刻记住使用完后要释放对象,但人往往会忘记释放.如果某个方法能够返回一个存在的对象,但该对象却不需要我们自己去释放,这将是一件多么让人兴奋的事!
那么如何返回一个存在但别人不持有的对象呢?
这就需要自动释放池了.
自动释放池( Autorelease Pool )提供了一个可以延迟给对象发送release消息的机制。它的使用场景比如从某方法返回一个对象时。给对象发送autorelease消息,那么该对象就被注册到自动释放池里面去了(这里需要注意的是如果给对象连续发送两条autorelease消息,那么该对象会被注册两次,当池子drain时,该对象将被发送两次release消息,这有可能导致过度release.),等到合适的时候自动释放池会被发送drain消息,此时自动释放池会给池子里面的每个对象都发送release消息,从而释放掉对象.
- (id)object
{
id obj = [[NSObject alloc] init];
[obj autorelease];
return obj;
}
id obj1 = [obj0 object]; //取得的对象存在,但自己不持有该对象.所以使用完后,不需要release.
如果想持有,则需发送retain消息.
[obj1 retain];
//此时自己就持有了该对象,当不在需要持有时,你有义务释放它.
...
[obj1 release];
dealloc是系统在对象被销毁时自动调用的,不能手动调用!
如果重写了dealloc方法,在MRC环境还需要调用[super dealloc]
(放在最后一句),但在ARC下禁止调用[super dealloc]
.
2.自动释放池
自动释放池( Autorelease Pool )提供了一个可以延迟给对象发送release消息的机制。它的使用场景比如从某方法返回一个对象时。给对象发送autorelease消息,那么该对象就被注册到自动释放池里面去了,等到合适的时候自动释放池会被发送drain消息,此时自动释放池会给池子里面的每个对象都发送release消息,从而释放掉对象.
这个"合适的时候"到底是什么时候呢?这就需要runloop了.iOS应用主线程的runloop(NSRunLoop)默认是开启的.当事件发生时,runloop会处理事件,执行我们写的代码.在处理事件之前,它会先创建好一个新的自动释放池对象(NSAutoreleasePool对象),等到处理完这个事件时,runloop会销毁这个NSAutoreleasePool对象,此时自动释放池会给池子里面的每个对象都发送release消息,从而释放掉对象.因此我们可以不用显示创建一个自动释放池.然而,在大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,那么里面的对象就不能被释放,这个时候就有可能产生内存不足的情况.在这种情况下,有必要在适当的地方生成,持有,废弃NSAutoreleasePool对象.
MRC的写法:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
ARC的写法:
@autoreleasepool
{
id obj = [NSMutableArray array];
}//对象在这里被释放,并销毁.
注意:ARC对自动释放在运行时做了一些优化.一个对象原本应注册到自动释放池中,但是有些情况下经ARC优化后,这个对象就省略了自动释放池的注册.从而缩短了这个对象的生命周期.所以在ARC下,某个不以 alloc/new/copy/mutableCopy开头的方法,返回的对象我们不应该假定它就是在自动释放池中.
以下是Clang3.9文档的说明
3.2.3 Unretained return values
A method or function which returns a retainable object type but does not return a retained value must ensure that the object is still valid across the return boundary.
When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, then leaves all local scopes, and then balances out the retain while ensuring that the value lives across the call boundary. In the worst case, this may involve an autorelease, but callers must not assume that the value is actually in the autorelease pool.
ARC performs no extra mandatory work on the caller side, although it may elect to do something to shorten the lifetime of the returned value.
举个例子:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"--------");
for (NSInteger i = 0; i < 100000000; i++)
{
People *p = [People productPeople];
}
NSLog(@"+++++++");
}
这段代码在MRC下将导致内存的急剧增长,并导致应用被系统直接干掉.
说明了-productPeople方法产生的autorelease对象由于自动释放池没被销毁前它里面的对象也不会被释放而导致内存爆涨.
而在ARC下,由于ARC对自动释放池有做优化,所以并没有引起内存的太大变化.