------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一,为什么要进行内存管理
1,由于移动设备的内存有限,所以每个APP所占的内存也是有限制的,当APP所占用的内存较多时,系统就会发出警告,这时就需要回收一些不需要继续使用的内存空间,比如回收一些不再使用的对象和变量等。
任何继承NSObject的对象,对其他的基本数据类型无效
本质原因是因为对象和其他数据类型在内存中的存储空间不一样,其他局部变量主要放于栈中,而对象存储在堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露。
2,内存管理的黄金法则:
苹果官方文档原话:The basic rule to apple is everything thatincreases the reference counter with alloc,[mutable]copy[WithZone:] or retainis in charge of the corresponding [auto]release.
即:如果一个对象使用了alloc,[multable]copy,retain,那么你必须使用相应的release或者autonrelease
3,内存管理类型分类
基本类型和C语言的类型:
int,short,char,struct,enum,union等类型
OC类型:任何继承于NSObject对象都属于OC的类型
内存管理实际上是对OC类型的内存管理,它对基本数据类型和C语言的类型不管用
4,OC对象在内存中的结构
所有的OC类型的对象结构中都包含一个retainCount的引用计数
每一个OC对象都有一个4个字节的retainCount的计数器,表示当前对象被引用的计数。如果计数器为0,那么就释放这个对象。
规则:
1>OC类中实现了引用计数器,对象知道自己当前被引用的次数
2>对象创建时计数器为1.
3>如果需要引用对象,可以给对象发送一个retain消息,对象计数器+1
4>使用release可以使对象计数器-1.
5>当计数器为0时,自动调用对象的dealloc函数,对象就会释放内存
6>计数器为0的对象不能再使用release和其他方法
5,举例说明:
比如有一个引擎类Engine,有一个汽车类Car,Car里面有一个Engine的实例变量,一个setter和getter方法,如下
#import "Car.h"
@implementation Car
-(void)setEngine:(Engine *)engine
{
_engine = engine;
}
- (Engine *)engine
{
return _engine;
}
- (void)dealloc
{
NSLog(@"Car is dealloc");
[super dealloc];
}
@end
在main方法里:
Engine *engine1 = [[Engine alloc]init];
[engine1 setID:1];
//在创建一个汽车,设置汽车的引擎
Car *car = [[Car alloc]init]
[car setEngine:engine1];
分析:现在有两个引用指向这个Engine对象,engine1和Car中的_engine,可是这个Engine对象的引用计数还未1,因为set方法中并没有使用retain。那么不管是那个引用调用release,那么林外一个都会引用都会指向一块释放掉的内存,那么肯定会发生错误。
setter方法改进:
- (void)setEngine:(Engine *)engine
{
_engine = [engine retain];
}
再在main中使用:
//先创建一个引擎
Engine* engine1=[[Engine alloc]init];
[engine1 setID:1];
//在创建一个汽车,设置汽车的引擎
Car* car=[[Car alloc]init];//retainCount=1
[car setEngine:engine1];//retainCount=2,因为使用了retain,所以retainCount=2,
//假设还有一个引擎
Engine* engine2=[[Engine alloc]init];
[engine2 setID:2];
//这个汽车要换一个引擎,自然又要调用settr方法
[car setEngine:engine2];
分析:在这里,汽车换了一个引擎,那么它的_engine就不在指向engine1的哪个对象的内存了,而是换成了engine2,也就是说engine1的哪个对象指向的内存的引用只有一个 可是它的retainCount是两个,这就是问题的所在了。所以仍然需要改进
再改进:
-(void)setEngine:(Engine*) engine
{
[_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
_engine=[engine retain];//多了一个引用,retainCount+1
}
//先创建一个引擎
Engine* engine1=[[Engine alloc]init];
[engine1 setID:1];
//在创建一个汽车,设置汽车的引擎
Car* car=[[Car alloc]init];//retainCount=1
[car setEngine:engine1];//retainCount=2,因为使用了retain,所以retainCount=2,
//如果进行了一个误操作,又设置了一次engine1
[car setEngine:engine1];
分析:那么,又要重新调用一次setter方法,这根本就是无意义的操作,浪费资源,所以要在设置之间加上判断
改进:
-(void)setEngine:(Engine*) engine
{
if(_engine!=engine){//判断是否重复设置
[_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
_engine=[engine retain];//多了一个引用,retainCount+1
}
}
现在setter方法基本没有问题了,那么在当我们要释放掉一个car对象的时候,必须也要释放它里面的_engine的引用,所以,要重写car的dealloc方法。
-(void)dealloc
{
[_engine release]; //在释放car的时候,释放掉它对engine的引用
[super dealloc];
}
所以,setter方法中应该是:
-(void)setEngine:(Engine*) engine
{
if(_engine!=engine){//判断是否重复设置
[_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
_engine=[engine retain];//多了一个引用,retainCount+1
}
}
dealloc的写法是:
-(void)dealloc
{
[_engine setEngine:nil]; //在释放car的时候,对setEngine设置为nil,它不仅会release掉,并且指向nil,即使误操作调用也不会出错。
[super dealloc];
}
property中的setter语法关键字
在property属性中有三个关键字定义关于展开setter方法中的语法,assgin(缺省),retain,copy。当然这三个关键字是互斥的。
1、assgin展开stter的写法
-(void)setEngine:(Engine*) engine
{
_engine=engine;
}
2、retain展开的写法
-(void)setEngine:(Engine*) engine
{
if(_engine!=engine){//判断是否重复设置
[_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
_engine=[engine retain];//多了一个引用,retainCount+1
}
}