为了开学的面试,就在博客里总结一下面试会问到的问题,今天就来谈谈内存管理,看到一篇文章非常不错,http://vinceyuan.cnblogs.com/,深入浅出,推荐大家去看看!
Objective-C使用一种(Retain Count)引用计数的机制来管理内存,在OC中,每个对象都持有自己的retain count,引用计数可以理解为就是一个计数器,当对象alloc创建的时候,会自动设置为1,当给对象发送retain消息的时候,引用计数会加1,当给对象发送release消息的时候,引用计数会减1,当引用计数为0的时候,对象会释放所占用的内存,这就是内存管理的机制,听起来比较容易吧,下面就进一步分析这种机制。
首先,我们应该知道为什么要这样做?我们经常在不同的对象中引用相同的对象,例如:假设我们用不同电脑远程连接到同一台服务器进行远程操作,转化成OC语言就是不同电脑对象引用相同的服务器对象,这时候有一台电脑在服务器上敲了shutdown命令,让服务器挂掉了,这时候我们所有电脑都连接不上去了,其他人就工作不了了,引用计数其实就像一个计数开关,只有当没有电脑连接的时候,计数为0,才允许执行shutdown命令(非常理想状态下,现实中可不要这样做)。
不同于java的GC回收机制,java中当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。OC中这种特有的retain count机制,给我们更多权限,让开发者去控制对象释放的时间以及如何去释放,所以我们得更加小心,过早的释放内存,可能会引起程序崩溃,长时间不释放占用的内存,程序在运行一段时间后可能会发生内存泄露。
Objective-C采用了引用计数(retain count)。对象的内部保存一个数字,表示被引用的次数。例如,某个对象被两个指针所指向(引用)那么它的retain count为2。需要销毁对象的时候,不直接调用dealloc,而是调用release。release会让retain count减1,只有retain count等于0,系统才会调用dealloc真正销毁这个对象。
ClassA *obj1 = [[ClassA alloc] init];//对象生成时,retain count = 1 [obj1 release]; //release使retain count减1,retain count = 0,dealloc自动被调用,对象被销毁
下面观看一个例子
ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1 ClassA *obj2 = obj1; //retain count = 1 [obj1 hello]; //输出hello [obj1 release]; //retain count = 0,对象被销毁 [obj2 hello]; [obj2 release];
obj2引用了obj1,此时retain count为1,当obj1执行完消息释放后,retain count=0,此时obj2变成了无效指针,这里再执行[obj2 release]会引起内存的过度释放
所以一定要谨记,不是alloc创建,而是指针赋值的时候,一定要retain,拿到对象的所有权
ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1 ClassA *obj2 = obj1; //retain count = 1 [obj2 retain]; //retain count = 2 [obj1 hello]; //输出hello [obj1 release]; //retain count = 2 – 1 = 1 [obj2 hello]; //输出hello [obj2 release]; //retain count = 0,对象被销毁
这样写的确可以解决问题,但是如果对象非常多得时候,这样的操作会不会太繁琐了点,有没有简单一点的解决办法?所以oc引入了自动释放池autorelease pool,这也不同于java的全自动垃圾回收
新生成的对象调用autorelease就可以了
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1 但无需调用release
如果存在指针赋值,与上面的代码也相似
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1 ClassA *obj2 = obj1; //retain count = 1 [obj2 retain]; //retain count = 2 [obj1 hello]; //输出hello //对于obj1,无需调用(实际上不能调用)release [obj2 hello]; //输出hello [obj2 release]; //retain count = 2-1 = 1
这里有个有趣的问题,retain count不是1么,还不能销毁呀,什么时候才能销毁呢?
所以我们得了解一下autorelease pool的原理机制。
1)autorelease pool不是天生的,需要手动创立。只不过在新建一个iphone项目时,xcode会自动帮你写好。autorelease pool的真名是NSAutoreleasePool。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2)NSAutoreleasePool内部包含一个数组(NSMutableArray),用来保存声明为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此对象加入autorelease pool中
3) NSAutoreleasePool自身在销毁的时候,会遍历一遍这个数组,release数组中的每个成员。如果此时数组中成员的retain count为1,那么release之后,retain count为0,对象正式被销毁。如果此时数组中成员的retain count大于1,那么release之后,retain count大于0,此对象依然没有被销毁,内存泄露。
那是不是有了自动释放池autorelease pool就万无一失了,其实不然
默认只有一个自动释放池
int main (int argc, const char *argv[]) { NSAutoreleasePool *pool; pool = [[NSAutoreleasePool alloc] init]; // do something [pool release]; return (0); } // main
所有标记为autorelease的对象在这个pool内被销毁,但是如果这个自动释放池里面含有大量autorelease的对象,还是容易造成内存不足的情况,比如说:
int main (int argc, const char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int i, j; for (i = 0; i < 100; i++ ) { for (j = 0; j < 100000; j++ ) [NSString stringWithFormat:@"1234567890"];//产生的对象是autorelease的。 } [pool release]; return (0); } // main
这种情况,大量内存被占用,只有poll销毁的时候,那些声明为autorelease对象才被销毁,这对于ios程序来说并不乐观,iphone内存本身有限,那有没有更好的解决办法,所以我们可以用autorelease嵌套机制来控制。
Objective-C程序中可以嵌套创建多个autorelease pool。在需要大量创建局部变量的时候,可以创建内嵌的autorelease pool来及时释放内存。
int main (int argc, const 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 < 100000; j++ ) [NSString stringWithFormat:@"1234567890"];//产生的对象是autorelease的。 [loopPool release]; } [pool release]; return (0); } // main