Objective-C内存管理

  内存管理有多重要?百度一堆理论知识看了忘,忘了又看,然后又忘,似乎总是揭不开内存管理这一层面纱。最近接触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];为例:

  1. 分配内存空间,存储对象(alloc)
  2. 初始化成员变量(init)
  3. 反回对象的指针地址(=)

手动内存管理原则:

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

原文地址:https://www.cnblogs.com/yif1991/p/5064806.html