内存管理


内存管理概述

  1. 内存管理
  • 内存管理的作用:存储数据
  • 声明一个变量,然后将数据存储过去
  1. 内存管理的范围:
  • 只需要管理存储在堆中的OC对象的回收,其他区域中的数据回事是系统自动管理的。
  1. 对象结束使用的时候才可以回收
  2. 引用计数器
  • 每一个对象都有一个属性,叫做retainCount。叫做饮用计数器,类型是unsigned ,占据8个字节。
  • 引用计数器的作用:用来记录当前这个对象由多少人在使用(当创建一个对象的时候,默认值为1)
  • 当这个d对象多一个人或少一个人使用,应该让计数器+1或者-1。
  • 当无人使用时(计数器为0),系统会自动回收。
  1. 如何操作引用计数器
  • 为对象发送一条retain消息,对象的引用计数器就会加1,当多1个人使用对象的时候才发
  • 为对象发送一条release消息,对象的引用计数器就会减1,当少1个人使用对象的时候才发
  • 为对象发送一条retainCount消息,就可以取到对象的引用计数器的值
  • 在对象被回收的时候,会自动调用对象的dealloc方法。
  1. 内存管理的分类
  • MRC:手动引用计数,手动内存管理
  • ARC: 自动引用
  1. 重写dealloc方法的规范
  • 必须要调用父类的dealloc方法,并且要放在最后一句代码。
-(void)dealloc
{
    NSLog(@"名字叫做%@的人挂了",_name);
    [super dealloc];
}
  1. 测试引用计数器
  • 新创建1个对象,这个对象的引用计数器默认值是1
  • 为0时,立即回收,并自动调用dealloc方法
  • 重写dealloc方法
-(void)dealloc
{
    NSLog(@"名字叫做%@的人挂了",_name);
    [super dealloc];
}
  • 发送retain消息,计数器加1 [p1 retain];

  • 发送release消息,计数器减1 [p1 release]

  • 在ARC机制下,retain release dealloc这些方法无法调用

  1. 内存管理的原则
  • 有对象创建,就要匹配一个release
  • retain的次数和release的次数要匹配
  • 谁用谁retain,谁不用谁release
  • 只有在多一个人用的时候才retain,少一个人用的时候才release

野指针与僵尸指针

  1. 野指针
  • c语言中的野指针:定义一个指针变量,没有初始化,这个指针指向随机的一块空间,这样的指针叫做野指针
  • OC中的野指针:指针指向的对象已经被回收了,这样的指针叫做野指针。
  1. 对象回收的本质
  • 内存的本质:申请一个变量,实际上就是向系统申请指定字节数的空间,这些空间就不会再分配给别人了。回收之后,代表这个空间就可以被别人使用了。但存储的数据还在。
  • 回收对象:与内存一致
  1. 僵尸对象
  • 1个已经被释放的对象,但是这个对象所占的空间还没有分配给别人,这样的对象叫做僵尸对象。
  • 当僵尸对象占用的空间还没有分配给别人的时候 ,可以通过野指针访问。
  • 分配给别人时,则不可以。
  1. 僵尸对象的实时监测机制
  2. 使用野指针访问僵尸对象会报错,如何避免僵尸对象错误
  • 当一个指针成为野指针以后,将这个指针的值设置为nil
  1. 无法 复活一个僵尸对象

  2. 出现僵尸对象错误的原因:

  • 在于。新旧对象是同一个对象
  • 解决的方案:当发现新旧对象是同一个对象的时候,什么都不用,只有新旧对象不是同一个对象的时候才relase旧的,retain新的。
-(void)setCar:(car *)car
{
	 if(_car!=car)//说明新旧对象不是同一对象
 	{
 		[_car release];
		_car = [car retain];
 	}
 }
 

单个对象的内存管理

  1. 内存泄露
  • 指的是一个对象没有及时的回收,在该回收的时候的没有被回收,一直驻留在内存当中,直到程序结束时才回收。
  1. 单个对象的内存泄露的情况
  • 有对象创建,而没有对应的relase
  • retain的次数和relase的次数不匹配
  • 在不适当的时候,为指针赋值为nil
  • 在方法中为传入的对象进行不适当的retain。

多个对象的内存管理

  1. a当属性时一个OC对象的时候,setter方法的写法
  2. 将传进来的对象赋值给当前对象的属性,代表传入的对象多了一个人使用,所以我们应该为这个传入的消息发送retain消息,再赋值。当当前对象销毁的时候,代表属性指向的对象少一个人使用。就应该在dealloc中relase。
  3. 代码写法:
-(void)setCar:(car *)car
{
	_car = [car retain];
}
- (void)dealloc
{
	[_car release]
	[super dealloc]
}
  • 当属性是一个OC对象的时候,setter方法照着上面那样写,其实还是有bug,当为对象的这个属性多次赋值的时候,会发生内存泄露
  • 发送泄漏的原因:当为属性赋值的时候,代表旧对象少一个人用,心对象多一个人使用,应该relase旧的,retain新的。
  1. 当我们将传入的Car对象赋值给_car属性的时候
  • 代表1:_car属性原本指向的对象少一个人使用
  • 代表2: 传入的对象多一个人使用
  • 所以,我们应该先将_car属性原本指向的对象release,再将传入的新对象retain。
-(void)setCar:(car *)car
{
	[_car release];
	_car = [car retain];
}
- (void)dealloc
{
	[_car release]
	[super dealloc]
}
  1. 特别注意
  • 我们每次管理的对象时)OC对象。
  • 所以,只有属性的类型时OC对象的时候,这个属性的setter方法才要像上面那样写。
  • 如果属性不是OC对象类型的,setter方法直接赋值就可以了

@property参数

  1. @property是可以带参数的 @property(参数1,参数2,参数3,参数4。。。)数据类型 参数名称

  2. 介绍一下@property的四组参数

  • 与多线程相关的两个参数:atomic,nonatomic
  • 与生成的setter方法的实现相关的参数:assignassign,retain
  • 与生成只读,写相关的参数:readonly,readwrite
  • 与生成的getter setter方法名字相关的参数:getter,setter
  1. 与多线程相关的参数
  • atomic (默认):加安全锁,安全,但效率低
  • nonatomic(建议使用):不加安全锁, 不安全,但效率高
  1. 与生成的setter方法的实现相关的参数
  • assign(默认):生成的setter方法的实现就是直接赋值
  • retain(OC对象时使用):添加MRC内存管理代码,但是不会自动的在dealloc中生成relase的代码,所以,我们还要自己手动的在dealloc中的release。
  1. 与生成只读,读写的方法
  • readonly:只生成getter,不生成setter
  • readwrite(默认):同时生成
  1. 生成getter,setter方法名称相关的参数
  • getter= getter方法名字,自定义名字
  • setter = setter方法名字:,自定义名字(注意:setter方法带参数,所以要加一个冒号)
  • 记住:如果使用该参数修改了名字,使用点语法编译器会自动转换为调用修改后的名字。
  • 一般情况下不要改
  • 无论什么时候都不要改setter方法的名字
  • 当属性是一个BOOL类型的时候,就修改为以is开头,以提高代码的可读性。

@class

  1. 当两个类相互包含的时候,就会出现循环引用的问题,就会造成无限递归的问题,而导致无法编译通过
  2. 解决方法:
  • 其中一边不要使用#import引入对方的头文件
  • 而是使用@class 类名; 来标注这是一个类,这样就可以在不引入对方头文件的情况下,告诉这个编译器这是一个类。
  • 在.m文件中再#import引入对方的头文件就可以了。
  1. @class与#import的区别
  • (#)import是将指定的文件的内容拷贝到写指令的地方
  • @class并不会拷贝任何内容,只是告诉编译器,这是一个类,这样编译器在编译的时候才可以知道这是一个类。
  1. 当两个对象相互引用的时候,a对象的属性时b对象,b对象的属性是a属性,这个时候,如果两边都使用retain,那么就会发生内存泄漏。
  • 解决方法:一边使用retain而另一边使用assign(在@property),这时,使用assign的那一端在dealloc中不再需要relesase了。
学习