• 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

  • 相关阅读:
    shellscript 02 find & xargs
    PL/SQL exception
    PL/SQL 游标
    Eclipse
    【数据存储】操作资源文件
    【AsynTask】Android异步加载一张图品
    【数据存储】利用IO流操作文件
    【数据存储】DOM操作
    【特效】手指滑动:水波纹
    【数据存储】SAX操作
  • 原文地址:https://www.cnblogs.com/yif1991/p/5064806.html
Copyright © 2020-2023  润新知