内存管理有多重要?百度一堆理论知识看了忘,忘了又看,然后又忘,似乎总是揭不开内存管理这一层面纱。最近接触iOS开发,需要了解手机APP的一些底层知识,现在总算觉得对内存管理的重要性有些理性的认识了。首先从APP闪退的原因说起。APP闪退通常有两种原因:一种是程序逻辑错误;另一种是内存消耗过多,被系统kill掉了。苹果手机里的APP操作流畅得益于苹果公司对APP内存消耗进行了限制,开发者开发的APP对内存的消耗如果超过了其规定的阈值是经常发生闪退的,这样用户体验差,就会遭到冷落,如此,开发者就喝西北风去了。这种霸王态度迫使开发者对内存进行精细管理,保证了iOS上APP的质量要比Android系统上的高。在MRC时代(iOS4.3之前),iOS开发对程序员的要求是非常高的,因此工资也普遍比Android程序员高。Android可做不到这点,因为Java使用垃圾回收机制来自动管理内存,将内存管理的控制权完全交给了JVM,因此不能精确管理内存。所以Android机一直在拼内存,拼硬件,什么双核、四核、八核、十六核等。
在OC中,任何继承自NSObject类的对象都需要我们进行内存管理,某些基本数据类型(如intfloatdoublestruct等)则不需要。理解内存管理首先要知道什么是引用计数,因为这个计数器是系统用来判断是否回收该对象的唯一依据:当我们的引用计数为0的时候,系统会毫不犹豫回收当前对象。引用计数器就是每个对象内部保存的一个与之相关的整数而已。
- 当使用alloc(不是init,alloc是分配内存,init是初始化)、new、或copy等创建一个对象时,其引用计数器被设置为1;
- 给对象发送一条retain消息(即调用该对象的retain方法,发送消息就是调用方法),可以使引用计数器值加1;
- 给对象发送一条release消息,可以使引用计数器值减1;
- 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收,OC也会自动向对象发送一条dealloc消息,通知对象你将要被销毁,一般会重写dealloc方法,在这里释放相关资源,一定不要直接手动调用dealloc方法(dealloc方法相当于对象的遗嘱);
- 可以给对象发送retainCount消息获得当前的引用计数器值。
OC中有两种内存管理方式,一种是手动管理(MRC,手动引用计数),一种是自动管理(ARC,自动引用计数),尽管在iOS4.3之后ARC的出现把开发人员从繁琐的内存管理工作中 解脱出来,而且现在的新项目一般都采用ARC,但是我们也有必要对MRC有所了解,因为ARC仅仅是Xcode编译器帮我们做了MRC时代需要手动去做的事,原理还是一样的,想要对底层原理理解得更深一些一定要好好研究下~
对象是如何被创建的?一般创建对象的过程有三步,以Person * p = [[Person alloc] init];为例:
- 分配内存空间,存储对象(alloc)
- 初始化成员变量(init)
- 反回对象的指针地址(=)
手动内存管理原则:
1.谁创建,谁释放(“谁污染谁治理”)。如果你通过alloc、new或copy来创建一个对象,那么你必须调用release或autorelease。
2.除了alloc、new或copy之外的方法创建的对象一般都被声明了autorelease;
3.谁retain,谁release。只要调用了retain,无论这个对象是如何生成的,你都要调用release。
手动管理内存主要是为了避免两种情况:野指针操作和内存泄漏(内存溢出)。一个一个来看:
野指针操作
野指针是指指向一块已经不存在的内存空间的指针。
1 void test() 2 { 3 //retainCount = 1 4 Person * p = [[Person alloc] init]; 5 6 p.age = 10; 7 NSLog(@"%@",p); 8 9 //retainCount = 0 10 //系统已经将p所指向的对象回收了 11 //EXC_BAD_ACCESS 访问了不可访问的内存空间 12 //被系统回收的对象我们称之为僵尸对象 13 //默认情况下xcode为了提高编码效率,不会时时检查僵尸对象 14 15 [p release]; 16 17 // NSLog(@"p所指向的对象回收了 %@",p); 18 // p.age = 20;//[p setAge:20]; 19 [p run]; 20 21 }
第15行代码已经将指针变量p所指向的对象释放,但第19行代码又对p进行访问,这就导致了野指针操作。如果你确定当前作用于中的对象已经不会再被使用了,为了防止野指针操作,通常我们会把不在使用的指针变量赋值为nil。
1 void test2() 2 { 3 Person * p = [[Person alloc] init]; 4 p.age = 20; 5 6 NSLog(@"%@",p); 7 8 [p release]; 9 10 p = nil; 11 12 p.age = 30;//[nil setAge:30]; 13 [p run]; //[nil run]; 14 //避免野指针操作的方法 [nil resele] 15 16 }
内存泄漏
内存泄漏是指不再被使用的对象一直存在内存中没有被销毁,这样会无休止地占用系统资源,可不是内存里破了个洞啊(以前我是这么认为的~)。
1 void test3() 2 { 3 //内存泄漏第一种情况 4 Person * p = [[Person alloc] init]; 5 p.age = 20; 6 NSLog(@"%@",p); 7 8 [p retain]; 9 10 [p retain]; 11 12 [p release]; 13 14 //内存泄漏第二种情况 15 //retainCount = 1 16 Person * p = [[Person alloc] init]; 17 p.age = 20; 18 [p run]; 19 20 p = nil; 21 22 [p release];//[nil release] 23 }
其实,OC里也有内存自动回收机制,叫自动释放池(autorelease pool)。一般可以将一些临时变量添加到自动释放池中,统一回收释放;当自动释放池销毁时,池里的所有对象都会调用一次release方法。这和.NET中的GC不一样。.NET中的GC会自动监测某个对象,如果没有人使用它,就自动将其释放。这里的自动释放池只能叫半自动。OC对象需要发送一条release消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)。autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会调用release。自动释放池创建的频率很高,可能会产生很多自动释放池,这些池会放在栈里(先进后出)。autorelease只是将对象放到自动释放池里,并没有改变其计数器,retain/release才会改变对象的计数器。自动释放池被销毁后,里面的对象不一定也销毁,只是其计数器减1而已,因为某些对象的计数器可能大于1。
1 #import <Foundation/Foundation.h> 2 #import "Student.h" 3 4 int main(int argc, const char * argv[]) 5 { 6 // @autoreleasepool代表创建一个自动释放池 7 @autoreleasepool { 8 Student *stu = [[[Student alloc] init] autorelease]; 9 10 //[stu autorelease]; 11 12 Student *stu1 = [[[Student alloc] init] autorelease]; 13 //[stu1 autorelease]; 14 15 // 这个stu2是自动释放的,不需要释放 16 Student *stu2 = [Student student]; 17 18 // 这个str是自动释放的,不需要释放 19 NSString *str = [NSString stringWithFormat:@"age is %i", 10]; 20 } 21 return 0; 22 }
上例中,在第20行代码出通过autoreleasepool创建的自动释放池就销毁了。
使用自动释放池需要注意以下几点:
- 在ARC下,应当使用autoreleasepool来创建自动释放池;
- 尽量避免对大内存使用该方法,对于这种延迟释放机制,还是尽量少用;
- 避免把大量循环操作放到同一个自动释放池中,这样每次循环创建的对象都会被放到自动释放池中,但有时候我们需要尽快释放这些大量的对象,以免影造后续操作。
- SDK中利用静态方法创建并返回的对象都是已经autorelease了的,不需要再进行release操作,如:[NSNumber numberWithInt:10];返回的对象是不需要再release的;但是通过[[NSNumber alloc] initWithInt:10]创建的对象需要release。
了解了MRC,ARC就无师自通了,前面说了,ARC就是由编译器在代码中自动加入retain/release操作,它是编译器特性,不是iOS运行时特性,更不是GC,只是一种代码静态分析工具。关于ARC的知识,更多可参考博文:http://blog.csdn.net/q199109106q/article/details/8565017。ARC也不是万能的,在ARC下也需要注意一些情况,参见http://www.jianshu.com/p/556ba33fa498