iOS中内存管理
2018-09-11 本文已影响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];
}
}