Objective-C中的内存回收机制简介

一、Objective-C的内存回收机制

在Objective-C中采用的是引用计数的内存回收方式。凡是继承NSObject的类生成的对象,当对象的计数为0,会对对象执行dealloc并回收。

二、alloc, retain, release

1、alloc:用来分配内存,在利用alloc生成分配了一个对象内存后,该对象的引用计数是1。

如:

ClassA *obj = [[ClassA alloc] init]; //retainCount 为1

2、release:用来将对象的引用计数减一, 当我们不在需要这个对象调用obj的release方法使引用计数变为0, obj的dealloc方法会被自动调用。

接上例:

[obj release]; //retainCount 减一变为0,obj的dealloc会被调用

3、 retain:用来将对象的引用计数加一。

如:

ClassA *obj = [[ClassA alloc] init]; //retainCount 为1

[obj retain]; //retainCount为2

//do something

[obj release]; //retainCount为1

[obj release]; //retainCount为0,调用dealloc 对象释放

在这个环节上容易出的错误:

ClassA *obj1 = [[ClassA alloc] init]; //ClassA里面有个 名为hello的function

ClassA *obj2 = obj1;

//do something with obj1 and obj2

[obj1 hello];

[obj1 release]; //对象已经释放掉了

[obj2 hello]; //崩溃,obj2已经是野指针了

[obj2 release];

三、autorelease, NSAutoreleasePool 和工厂方法

1 、NSAutoreleasePool:在开发的过程中会出现内存申请者经常会出现内存申请者和内存使用者之间的交互问题,比如,一个函数内部申请了内存,做了一些操作,然后返回这块内存。这个时候调用者要负责对这块内存执行清理工作,在调用栈很深的情况下,很容易产生内存泄露。而且这种内存解决方案破坏了内存由谁生成由谁释放的原则。

为了解决这个问题,产生了NSAutoreleasePool。使用方法如下:

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; //创建一个release pool

ClassA *obj = [[[ClassA alloc] init] autorelease]; //创建一个对象并将它加入release pool

[obj retainCount]; //retainCount为1

//do something

[pool release]; //在release pool被释放的时候将obj的引用计数自动减一,这时

//obj的releaseCount变为了0,obj被释放

对象在被autorelease时他的引用计数并没有立即减少,而是在release pool被释放的时候引用计数才减一的。

在对象被

2、工厂方法:在UIKit提供的类中经常会看到static方法创建出的对象,如:

NSNumber *numObj = [NSNumber numberWithInt: 1];

从设计模式的角度上讲这个static方法创建了,一个对象,这中利用公共方法创建对象的模式叫做工厂方法。

在Objective-C中的程序设计中有个规则,就是这种工厂方法返回的对象都是不需要调用者release的,因为虽然他们现在的引用计数为1,但是他会自动释放。(因为他们在返回对象的时候都对其调用了autorelease)

3、深入讲解NSAutoreleasePool

NSAutoreleasePool是苹果公司实现的类,我们无法知道他是怎么实现的,但是可以确定的是,在对象调用autorelease的时候,距离对象最近的NSAutoreleasePool将记录该对象,并在autorelease pool释放时将对象的计数减一。

NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init]; //创建第一个release pool

ClassA *obj1 = [[ClassA alloc] init] autorelease]; //生成一个对象,调用autorelease,对象

//被加载到pool1里,obj1的引用计数为1

[obj1 retain]; //obj1引用计数加1,变为2

NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init]; //创建第二个release pool

ClassA *obj2 = [[ClassA alloc] init] autorelease]; //生成另一个,调用autorelease,对象

//被加载到pool2里,obj2的引用计数为1

[obj1 autorelease]; //obj1调用autorelease,被加载到pool2里,引用计数为2

[pool2 release]; //释放pool2,obj1的引用计数减1,变为1

//obj2的引用计数减1,变为0,obj2对象释放

[poo1 release]; //释放pool1,obj1的引用计数减1,变为0,obj1对象释放

NSAutoreleasePool是需要在release自身的时候对挂在它上面的对象(调用过autorelease的对象)进行引用计数减一的,但是,我们能看见的显式创建的NSAutoreleasePool只在main函数中有创建,难道程序在运行过程中调用autorelease的对象必须在程序退出时释放么?

绝对不可能,如果是在main函数结束时释放,在程序运行中必然造成内存使用量一直上升。 实际上,在主线程运行中,系统会自动创建一个NSAutoreleasePool并在一次绘图完成以后释放这个pool。(忘记看了那份文档说的了,自己实验的结果是如此,等我找到了文档再分享给大家)

四、声明一个property,利用assign, retain或者copy

利用property可以快速创建一个属性

利用property创建一个属性之后,根据设置(assign, retain或者copy)系统生成的Setter和Getter是不同的。(以NSString为例)

在调用self.name进行赋值时会调用setter(setName:)在进行取值时会调用getter(getName)

公用部分:

//.h文件

@property (nonatomic, XXXX) NSString *name;

//.m文件

@synthesize name = _name;

1. 如果XXXX是assign,系统会自动生成setter和getter如下:

setter:

- (void) setName: (NSString *) newName {

_name = newName;

}

getter:

- (NSString *) getName {

return _name;

}

2. 如果XXXX是retain,系统会自动生成setter和getter如下:

setter:

- (void)setName: (NSString *)newName {

if (_name == newName) {

return;

}

[_name release]; //旧的对象引用计数减一

_name = [newName retain]; //将新的对象引用计数加一

}

getter:

- (NSString *) getName {

return _name;

}

3.如果XXXX是copy,系统会自动生成setter和getter如下:

setter:

- (void)setName: (NSString *)newName {

if (_name == newName) {

return;

}

[_name release]; //旧的对象引用计数减一

_name = [newName copy]; //将新的对象引用计数加一,

}

getter:

- (NSString *) getName {

return _name;

}

_name是一个属性,如果对其直接做赋值操作,是不会对对象的引用计数产生影响的,因此尽量不要直接对其进行赋值操作,可以避免很多错误。

如果是用retain和copy声明的属性,有一些场景需要直接对_name执行释放(如需要立即释放内存的dealloc,关掉网络请求等),这时要直接调用[_name release]将内存释放,并且将_name变量设置为nil。

if (_name != nil) {

[_name release], _name = nil;

}

//或者

self.name = nil;

//错误的方式

if(_name != nil) {

[_name release]; // _name引用计数为0,_name 所指被释放

}

self.name = nil; //这里肯定崩溃, 因为在前面,_name已经被release了。

//但由于_name没有赋值为nil,变成了野指针。

//前面说过,self.name执行的setter会先将

//旧的_name执行release,这时造成崩溃。

上面这个错误是典型的由于野指针引起的崩溃,请大家务必注意。

五、其他注意事项

1. 自己创建了thread后,第一件事就是创建NSAutoreleasePool。(来自苹果线程安全手册)

2. NSArray,NSDictionary,NSSet等容器类会自动将引用添加到其中的对象引用计数加一,当移除对象或者释放容器对象自身的时候引用计数减一。