美文网首页
iOS中内存管理

iOS中内存管理

作者: 皆为序幕_ | 来源:发表于2018-09-11 16:12 被阅读0次

    内存管理重要性

    • 移动设备的内存极其有限,每个APP所占的内存都是有限的
    • 下列行为就会增加一个APP的内存占用
      • 创建一个OC对象
      • 定义一个变量
      • 调用一个函数或者方法
      • 当APP所占用内存较多时,系统会发出内存警告,这时得回收一些不需要再次使用的内存空间,比如收一些不需要使用的对象、变量等
    • 若果APP占用内存过大,系统会强制关闭APP,造成闪退,影响用户体验

    内存管理

    • 内存管理:就是管理内存的分配和清除

    • 内存管理涉及的操作有:

      • 分配内存:比如创建一个对象,会增加内存占用
      • 清楚内存:比如销毁一个对象,能减少内存占用
    • 内存管理范围

      • 任何继承NSObject的对象
      • 对其他非对象类型无效(int char float double struct enum 等)
        注:为什么只有OC对象才需要进行内存管理的本质原因
        • OC对象存放于堆里面(堆内存系统不会自动释放,需要手动释放)
        • 非OC对象一般放在栈里面(栈内存会被系统自动回收释放)

    堆和栈

    • 栈(操作系统):由操作系统自动分配释放空间,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈的先进后出
    • 堆(操作系统):一般由程序员分配释放空间,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表
    //这个方法结束后,栈里的变量a、p会被回收,堆里的Person对象还会留在内存中,因为它的引用计数还是1
    -(void)doSomething{
        //a:栈
        int a = 10;
        //p:栈
        //Person:堆
        Person *p = [[Person alloc]init];
    }
    

    引用计数器

    OC语言使用引用计数来管理内存,每一个对象都有一个可以递增递减的计数器,如果引用这个对象,那么这个对象的引用计数递增,如果不用了,那么这个对象引用计数递减,直到引用计数为0,这个对象就可以销毁了

    引用计数器的作用
    • 表示对象被引用的次数
    • 查看某对象的引用计数调用- (NSUInteger)retainCount
    • 当使用alloc 、new 、copy创建一个对象时,对象的引用计数器默认为1
    • 当没有任何人使用这个对象时,系统才会回收这个对象
    • 当对象的引用计数器为0时,对象占用的内存才会被回收
    • 如果对象的引用计数不为0,这个对象占用的内存就不可能被回收(除非整个程序已经退出)
    引用计数器的原理
    • 给对象发送一条retain消息,这个对象的引用计数值+1
    • 给对象发送一条release消息,这个对象的引用计数值-1
    • 给对象发送retainCount消息,可以获得当有对象的引用计数
      注: release并不代表销毁或回收对象,仅仅是计数器-1
    属性存取方法中的内存管理(retain、copy、assign)
    • readonly: 只会生成getter方法
    • readwrite:既会生成getter也会生成setter方法,默认什么不写就是readwrite
    • getter:可以给生成的getter方法起一个名字
    • setter:可以给生成的setter方法起一个名字
    • retain: 会自动帮我们生成setter方法内存管理的代码
    • assign:不会帮我们生成setter方法内存管理的代码,仅仅只会生成普通的getter/setter方法,默认什么都不写就是assign
    • nonatomic:多线程中使用,性能低(默认)
    • atomic:多线程中使用,性能高
    - (void)setName:(NSString *)name{
        if (_name != name) {//先判断传进来的是不是新对象
            [_name release];  //把_name以前的对象release
            _name = [name retain]; //把name对象的地址赋给_name,这时name和_name共同指向同一个对象
        }
    }
    
    - (void)setName:(NSString *)name{
        if (_name != name) {
            [_name release];//把_name以前的对象release
            _name = [name copy];//把name对象的地址拷贝一份给_name
        }
    }
    -(void)setName:(NSString *)name{
            _name = name;    //直接赋值
    }
    
    dealloc方法
    • 当一个对象的引用计数器值为0的时候,这个对象即将被释放,其占用的内存被系统收回
    • 对象即将被销毁时系统会自动给对象发送一条dealloc的消息(因此,从dealloc方法有没有被调用就可以判断出对象是否被销毁)
    • dealloc方法重写
      • 一般重写dealloc方法,在这里释放相关资源(移除监听者、移除coreFoundation对象等等)
      • 在MRC下,一旦重写dealloc方法,就必须调用[super dealloc],并且放在最后调用
    • 使用注意
      • 不直接调用dealloc
      • 不要在dealloc方法中调用其他方法
      • 一旦对象被回收了,它占的内存就不再可用
    野指针和空指针
    • 野指针
      • 只要一个对象被释放了,我们就称这个对象为“僵尸对象”
      • 当一个指针指向一个僵尸对象,我们就称为这个指针为野指针
      • 只要给一个野指针发送消息就会报错
    • 空指针
      • 没有指向存储空间的指针(里面存的是nil,也就是0)
      • 为了避免给野指针发送消息会报错,一般情况,当一个对象被释放后我们就会将这个对象的指针置为空指针
      • 注:在OC中,给空指针发送消息是不会报错的

    ARC

    ARC是新的LLVM3.0编译器的一项特性,在工程中使用非常简单,不用再写release、retain、autorelease三个关键字。当开启ARC时,编译器将自动在代码合适的地方插入release、retain和autorelease。

    • ARC注意点和优点
    • ARC注意点
      • ARC是编译器的特性,而不是运行时的特性
      • ARC不是其他语言中的垃圾回收,有着本质区别,其他语言是定时查看,ARC就是写好的代码,直接执行就可以
    • ARC优点
      • 完全消除了手动管理内存的繁琐
      • 基本上能够避免内存泄漏
      • 有时还能更加快速,因为编译器还可以执行某些优化
    • ARC的判断原则
      只要没有强指针指向对象,对象就会被释放
      • 强指针

         //默认所有指针变量都是强指针
         Person *p = [[Person alloc]init]; 
        
          //被_strong修饰的指针 
          __strong Person *p = [[Person alloc]init]; 
        
      • 弱指针(在开发中,千万不要使用一个弱指针保存刚刚创建的对象,会被立即释放)

        //被__weak修饰的指针
        __weak Person *p = [[Person alloc]init]; 
        
    循环引用

    由于对象间彼此引用,无法释放,所以,循环引用会引发内存泄漏

    • 如果A对象拥有B对象,而且B对象又拥有A对象,此时会形成循环retain
    @class Animal;
    @interface Person : NSObject
    @property (nonatomic,strong) Animal *animal;
    @end
    
    @class Person;
    @interface Animal : NSObject
    @property (nonatomic, strong) Person *person;
    @end
    
    • 如何解决这个问题,不要让A retain B, B retain A
      ARC中保存对象不用assign,用weak,assign是专门用于保存基本数据类型的,保存对象用weak
    @class Animal;
    @interface Person : NSObject
    @property (nonatomic,strong) Animal *animal;
    @end
    
    @class Person;
    @interface Animal : NSObject
    @property (nonatomic, weak) Person *person;
    @end
    

    autorelease

    autoreleasepool用于存放那些需要在稍后某个时刻释放的对象,清空自动释放池时,系统会向其中的对象发送release消息

    花括号定义了自动释放池的范围,左花括号开始创建,右花括号处自动释放,在此范围的末尾处,括号内的对象回收到release消息
    @ autoreleasepool{
    
    }
    

    注:这里只是发送一次release消息,如果当时引用计数不为0,则该对象依然不会释放

    • autorelease方法会返回对象本身(MRC)
      Penson *p = [Person new];
      p = [p autorelease];
      
    • 调用完autorelease 方法后,对象的计数器不变(MRC)
      Person *p = [Person new];
      p = [p autorelease];
      NSLog(@"count= %d",[p retainCount]);//1
      
    autoreleasepool的原理

    autorelease 实际上只是release的调用延迟了,对于每一个autorelease,系统只是把该Object放入了当前的autorelease pool中,当pool 被释放时,该pool中的所有Object会被调用release

    autorelease的好处
    • 不用关心对象释放时间
    • 不用关心什么时候调用release
    //创建一个自动释放池
    @autoreleasepool{    
    
       Person *p = [[Person alloc]init];
       //不用关心对象什么时候释放,只要能够访问p的地方都可以使用p
       //只要调用了autorelease,那就不用调用release
       p = [p autorelease];
       
    }
    //自动释放池销毁了,给自动释放池中所有的对象发送一条release消息
    
    autorelease的注意事项
    • 一定要在自动释放池中调用autorelease,才会将对象放入自动释放池(MRC)
    • 在自动释放池创建了对象,一定要调用autorelease,才会将对象放入自动释放池中(MRC)
     @autoreleasepool{
         Person *p =[[[Person alloc]init] autorelease];
     }
    
    • 不要在自动释放池中使用比较消耗内存的对象
    @autoreleasepool{
       Person *p =[[[Person alloc]init] autorelease];
       //一万行代码
    }
    
    • 尽量不要再自动释放池中使用循环,特别的循环次数多的
    @autoreleasepool{
      for   (int i= 0;i < 99999; i ++){
         //每次调用一次就会创建一个新的对象
         //每个对象都会占用一个存储块
        Person *p =[[[Person alloc]init] autorelease];
      }
    }//循环里创建对象会一直在池中,只有执行到这里才会释放
    
    • 一个程序中可以创建N个自动释放池,并且自动释放池可以嵌套,如果存在多个自动释放池,那么自动释放池会以“栈”的形式存储,先进后出
    @autoreleasepool{//创建第一个自动释放池
           @autoreleasepool{//创建第二个自动释放池
               @autoreleasepool{//创建第三个自动释放池
            }//销毁第一个自动释放池
        }//销毁第二个自动释放池
    }//销毁第三个自动释放池
    
    合理利用autoreleasepool可以降低内存峰值(ARC)

    把循环内的代码包裹在autoreleasepool中,那么在循环中自动释放对象就会放在这个池中,这样内存峰值就会降低(内存峰值:app在某个特定的时段内最大内存用量)

    for(int i= 0;i < 99999; i ++){
        @autoreleasepool{
          Person *p =[[Person alloc]init];
          [array addObject:p];
        }
    }
    

    相关文章

      网友评论

          本文标题:iOS中内存管理

          本文链接:https://www.haomeiwen.com/subject/vpyjiftx.html