iOS中内存管理

2023-11-22 08:39:49 浏览数 (3)

内存管理重要性

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

内存管理

  • 内存管理:就是管理内存的分配和清除
  • 内存管理涉及的操作有:
    • 分配内存:比如创建一个对象,会增加内存占用
    • 清楚内存:比如销毁一个对象,能减少内存占用
  • 内存管理范围
    • 任何继承NSObject的对象
    • 对其他非对象类型无效(int char float double struct enum 等) 注:为什么只有OC对象才需要进行内存管理的本质原因
      • OC对象存放于堆里面(堆内存系统不会自动释放,需要手动释放)
      • 非OC对象一般放在栈里面(栈内存会被系统自动回收释放)

堆和栈

  • 栈(操作系统):由操作系统自动分配释放空间,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈的先进后出
  • 堆(操作系统):一般由程序员分配释放空间,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表
代码语言:javascript复制
//这个方法结束后,栈里的变量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:多线程中使用,性能高
代码语言:javascript复制
- (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的判断原则 只要没有强指针指向对象,对象就会被释放

强指针

代码语言:javascript复制
 //默认所有指针变量都是强指针
 Person *p = [[Person alloc]init]; 
代码语言:javascript复制
  //被_strong修饰的指针 
  __strong Person *p = [[Person alloc]init]; 

弱指针(在开发中,千万不要使用一个弱指针保存刚刚创建的对象,会被立即释放)

代码语言:javascript复制
//被__weak修饰的指针
__weak Person *p = [[Person alloc]init]; 
循环引用

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

  • 如果A对象拥有B对象,而且B对象又拥有A对象,此时会形成循环retain
代码语言:javascript复制
@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
代码语言:javascript复制
@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消息

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

}

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

autorelease方法会返回对象本身(MRC)

代码语言:javascript复制
Penson *p = [Person new];
p = [p autorelease];

调用完autorelease 方法后,对象的计数器不变(MRC)

代码语言:javascript复制
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
代码语言:javascript复制
//创建一个自动释放池
@autoreleasepool{    

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

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

代码语言:javascript复制
for(int i= 0;i < 99999; i   ){
    @autoreleasepool{
      Person *p =[[Person alloc]init];
      [array addObject:p];
    }
}

0 人点赞