一 、基本原理
Objective-c的内存管理机制与.Net/java那种全自动的垃圾回收机制是不同的,它本质上还是C语言的手动管理方式,只不过稍微加了些自动方法。
1、 Objective-c的对象生成之后,需要一个指针来指向它。
ClassA *class1 = [[ClassA alloc] init];
2、objective-c的对象在使用完成之后不会自动销毁,需要执行dealloc来释放空间(销毁),否则内存泄露。
[class1 dealloc];
这带来一个问题,下面代码中class2是否需要调用dealloc?
ClassA *class1 = [[ClassA alloc] init];
ClassA *class2 = class1;
[class1 hello];//输出hello
[class1 dealloc];
[class2 hello];//能够执行这一行和下一行吗?
[class2 dealloc];
不能,因为class1和class2 只是指针,它们指向同一个对象,[class1 dealloc]已经销毁这个对象了,不能再调用[class2 hello]和[class2 dealloc]. class2 实际上是个无效指针。
3、Objective-c采用了引用计数(ref count或者retain count).对象的内部保存一个数字,表示被引用的次数。例如,某个对象被两个指针所指向(引用)那么它的retain count为2.需要销毁对象的时候,不直接调用dealloc,而是调用release.release会让retain count减一,只有 retain count等于0,系统才会调用dealloc真正销毁这个对象。
ClassA *class1 = [[ClassA alloc] init];//对象生成时,retain count = 1;
[class1 release];//release使retain count减1,retain count = 0,dealloc自动被调用,对象被销毁
我们回头看看刚刚那个无效指针的问题,把dealloc改成release解决了吗?
ClassA *class1 = [[ClassA alloc] init];//retain count = 1
ClassA *class2 = class1;//retain count = 1
[class1 hello];//输入hello
[class1 release];//retain count = 0,对象被销毁
[class2 hello];
[class2 release];
[class1 release]之后,class2依然是个无效指针。问题依然没有解决。
4、 Objective-c指针赋值时,retain count 不会自动增加, 需要手动 retain.
ClassA *class1 = [[ClassA alloc] init];//retain count = 1;
ClassA *class2 = class1;//retain count = 1;
[class2 retain];//retain count = 2
[class1 hello];// 输出hello
[class1 release];//retain count = 2 - 1 = 1
[class2 hello];输出hello
[class2 release];retain count = 0,对象被销毁
问题解决!注意,如果没有调用[class2 release],这个对象的retain count 始终为1,不会被销毁,内存泄露。
这样的确不会内存泄露,但似乎有点麻烦,有没有简单点的方法?
5、Objective-c中引入了autorelease pool(自动释放对象池),在遵守一些规则的情况下,可以自动释放对象。
(autorelease pool依然不是.Net/java那种全自动的垃圾回收机制)
5.1、新生成的对象,只要调用autorelease就行了,无需调用release!
5.2 对于存在指针赋值的情况,代码与前面类似。
ClassA *class1 = [[[ClassA alloc] init] autorelease];//retain count = 1
ClassA *class2 = class1;//retain count = 1;
[class2 retain];//retain count = 2
[class1 hello];//输出hello
//对于 class1,无需调用(实际上不能调用)release
[class2 hello];//输出hello
[class2 release];//retain count = 2 - 1 = 1
6 autorelease pool原理剖析。
6.1 autorelease pool不是天生的,需要手动创立。只不过在新建一个iphone 项目时,xcode会自动帮你写好。autorelease pool的真名是NSAutoreleasePool.
6.2 NSAutoreleasePool 内部包含一个数组( NSMutableArray), 用来保存声明为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。
ClassA *class1 = [[[ClassA alloc] init] autorelease];//retain count = 1,把此对象加入autorelease pool中
6.3 NSAutoreleasePool 自身在销毁的时候,会遍历一遍这个数组,release 数组中的每个成员。如果此时数组中成员的retain count为1,那么release之后,retain count为0,对象正式被销毁。如果此时数组中成员的 retain count 大于1,那么 release之后,retain count大于0,此对象依然没有被销毁,内存泄露。
6.4 默认只有一个autorelease pool,通常类似于下面这个例子。
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
// do something
[pool release];
return (0);
}
所有标记为autorelease 的对象都只有在这个pool销毁时才被销毁。如果你有大量的对象标记为autorelease, 这显然不能很好的利用内存,在iphone这种内存受限的程序中很容易造成内存不足的。
int main(int agrc,char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i,j;
for (i = 0; i < 100; i++)
{
for (j = 0; j < 10000; j++)
{
[NSString stringWithFormat:@"1234567890"];//
}
}
[pool release];
return 0;
}
运行时通过监控工具可以发现使用的内存在急剧增加,直到pool 销毁时才被释放。
Objective-c程序中可以嵌套创建多个autorelease pool.在需要大量创建局部变量的时候,可以创建内嵌的autorelease pool 来及时释放内存。
int main(int agrc,char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i,j;
for (i = 0; i < 100; i++)
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
for (j = 0; j < 10000; j++)
{
[NSString stringWithFormat:@"1234567890"];//
}
[loopPool release];
}
[pool release];
return 0;
}