内存管理概述
- 内存管理
- 内存管理的作用:存储数据
- 声明一个变量,然后将数据存储过去
- 内存管理的范围:
- 只需要管理存储在堆中的OC对象的回收,其他区域中的数据回事是系统自动管理的。
- 对象结束使用的时候才可以回收
- 引用计数器
- 每一个对象都有一个属性,叫做retainCount。叫做饮用计数器,类型是unsigned ,占据8个字节。
- 引用计数器的作用:用来记录当前这个对象由多少人在使用(当创建一个对象的时候,默认值为1)
- 当这个d对象多一个人或少一个人使用,应该让计数器+1或者-1。
- 当无人使用时(计数器为0),系统会自动回收。
- 如何操作引用计数器
- 为对象发送一条retain消息,对象的引用计数器就会加1,当多1个人使用对象的时候才发
- 为对象发送一条release消息,对象的引用计数器就会减1,当少1个人使用对象的时候才发
- 为对象发送一条retainCount消息,就可以取到对象的引用计数器的值
- 在对象被回收的时候,会自动调用对象的dealloc方法。
- 内存管理的分类
- MRC:手动引用计数,手动内存管理
- ARC: 自动引用
- 重写dealloc方法的规范
- 必须要调用父类的dealloc方法,并且要放在最后一句代码。
-(void)dealloc
{
NSLog(@"名字叫做%@的人挂了",_name);
[super dealloc];
}
- 测试引用计数器
- 新创建1个对象,这个对象的引用计数器默认值是1
- 为0时,立即回收,并自动调用dealloc方法
- 重写dealloc方法
-(void)dealloc
{
NSLog(@"名字叫做%@的人挂了",_name);
[super dealloc];
}
-
发送retain消息,计数器加1
[p1 retain];
-
发送release消息,计数器减1
[p1 release]
-
在ARC机制下,retain release dealloc这些方法无法调用
- 内存管理的原则
- 有对象创建,就要匹配一个release
- retain的次数和release的次数要匹配
- 谁用谁retain,谁不用谁release
- 只有在多一个人用的时候才retain,少一个人用的时候才release
野指针与僵尸指针
- 野指针
- c语言中的野指针:定义一个指针变量,没有初始化,这个指针指向随机的一块空间,这样的指针叫做野指针
- OC中的野指针:指针指向的对象已经被回收了,这样的指针叫做野指针。
- 对象回收的本质
- 内存的本质:申请一个变量,实际上就是向系统申请指定字节数的空间,这些空间就不会再分配给别人了。回收之后,代表这个空间就可以被别人使用了。但存储的数据还在。
- 回收对象:与内存一致
- 僵尸对象
- 1个已经被释放的对象,但是这个对象所占的空间还没有分配给别人,这样的对象叫做僵尸对象。
- 当僵尸对象占用的空间还没有分配给别人的时候 ,可以通过野指针访问。
- 分配给别人时,则不可以。
- 僵尸对象的实时监测机制
- 使用野指针访问僵尸对象会报错,如何避免僵尸对象错误
- 当一个指针成为野指针以后,将这个指针的值设置为nil
-
无法 复活一个僵尸对象
-
出现僵尸对象错误的原因:
- 在于。新旧对象是同一个对象
- 解决的方案:当发现新旧对象是同一个对象的时候,什么都不用,只有新旧对象不是同一个对象的时候才relase旧的,retain新的。
-(void)setCar:(car *)car
{
if(_car!=car)//说明新旧对象不是同一对象
{
[_car release];
_car = [car retain];
}
}
单个对象的内存管理
- 内存泄露
- 指的是一个对象没有及时的回收,在该回收的时候的没有被回收,一直驻留在内存当中,直到程序结束时才回收。
- 单个对象的内存泄露的情况
- 有对象创建,而没有对应的relase
- retain的次数和relase的次数不匹配
- 在不适当的时候,为指针赋值为nil
- 在方法中为传入的对象进行不适当的retain。
多个对象的内存管理
- a当属性时一个OC对象的时候,setter方法的写法
- 将传进来的对象赋值给当前对象的属性,代表传入的对象多了一个人使用,所以我们应该为这个传入的消息发送retain消息,再赋值。当当前对象销毁的时候,代表属性指向的对象少一个人使用。就应该在dealloc中relase。
- 代码写法:
-(void)setCar:(car *)car
{
_car = [car retain];
}
- (void)dealloc
{
[_car release]
[super dealloc]
}
- 当属性是一个OC对象的时候,setter方法照着上面那样写,其实还是有bug,当为对象的这个属性多次赋值的时候,会发生内存泄露
- 发送泄漏的原因:当为属性赋值的时候,代表旧对象少一个人用,心对象多一个人使用,应该relase旧的,retain新的。
- 当我们将传入的Car对象赋值给_car属性的时候
- 代表1:_car属性原本指向的对象少一个人使用
- 代表2: 传入的对象多一个人使用
- 所以,我们应该先将_car属性原本指向的对象release,再将传入的新对象retain。
-(void)setCar:(car *)car
{
[_car release];
_car = [car retain];
}
- (void)dealloc
{
[_car release]
[super dealloc]
}
- 特别注意
- 我们每次管理的对象时)OC对象。
- 所以,只有属性的类型时OC对象的时候,这个属性的setter方法才要像上面那样写。
- 如果属性不是OC对象类型的,setter方法直接赋值就可以了
@property参数
-
@property是可以带参数的
@property(参数1,参数2,参数3,参数4。。。)数据类型 参数名称
-
介绍一下@property的四组参数
- 与多线程相关的两个参数:atomic,nonatomic
- 与生成的setter方法的实现相关的参数:assignassign,retain
- 与生成只读,写相关的参数:readonly,readwrite
- 与生成的getter setter方法名字相关的参数:getter,setter
- 与多线程相关的参数
- atomic (默认):加安全锁,安全,但效率低
- nonatomic(建议使用):不加安全锁, 不安全,但效率高
- 与生成的setter方法的实现相关的参数
- assign(默认):生成的setter方法的实现就是直接赋值
- retain(OC对象时使用):添加MRC内存管理代码,但是不会自动的在dealloc中生成relase的代码,所以,我们还要自己手动的在dealloc中的release。
- 与生成只读,读写的方法
- readonly:只生成getter,不生成setter
- readwrite(默认):同时生成
- 生成getter,setter方法名称相关的参数
- getter= getter方法名字,自定义名字
- setter = setter方法名字:,自定义名字(注意:setter方法带参数,所以要加一个冒号)
- 记住:如果使用该参数修改了名字,使用点语法编译器会自动转换为调用修改后的名字。
- 一般情况下不要改
- 无论什么时候都不要改setter方法的名字
- 当属性是一个BOOL类型的时候,就修改为以is开头,以提高代码的可读性。
@class
- 当两个类相互包含的时候,就会出现循环引用的问题,就会造成无限递归的问题,而导致无法编译通过
- 解决方法:
- 其中一边不要使用#import引入对方的头文件
- 而是使用@class 类名; 来标注这是一个类,这样就可以在不引入对方头文件的情况下,告诉这个编译器这是一个类。
- 在.m文件中再#import引入对方的头文件就可以了。
- @class与#import的区别
- (#)import是将指定的文件的内容拷贝到写指令的地方
- @class并不会拷贝任何内容,只是告诉编译器,这是一个类,这样编译器在编译的时候才可以知道这是一个类。
- 当两个对象相互引用的时候,a对象的属性时b对象,b对象的属性是a属性,这个时候,如果两边都使用retain,那么就会发生内存泄漏。
- 解决方法:一边使用retain而另一边使用assign(在@property),这时,使用assign的那一端在dealloc中不再需要relesase了。