一、为什么要进行内存管理 系统资源有限,iOS会为每个运行的程序分配30M的内存,超过20M会收到内存警告,超过30M将会终止应用程序。因此,要及时回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等,以保证应用程序能正常运行。 二、需要管理的内存应用程序在运行过程中,会占用一定栈空间和堆空间,也就是说,应用程序运行过程中的数据,有的是放在栈中,有的是放在堆中。栈中的数据由系统维护,无需开发人员来管理,而堆中的数据需要程序员来维护。 堆空间由开发人员请求分配的,比如开发人员发送一条alloc消息创建一个对象,实际上就向堆空间申请了一块内存,用于存储创建的对象。对象存储于堆中,当代码块结束时,这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,若对象依然存在于内存中,就会造成内存泄露。 在这里需要注意两点: (1)基本数据类型一般放在栈中,不需要进行内存管理; (2)创建对象时,指向对象的指针放在栈中,由系统维护,而指针指向的对象,则是放在堆中,需要开发人员维护。 三、对象的结构与内存管理机制1、C和C++内存管理的不足在C和C++中,若有3个指针指向同一个对象,任何一个指针调用了free方法释放内存,其余的引用在不知道的情况下继续使用这块内存的时候,就会出现问题。因此,何时由谁去释放这块内存,这就是C和C++在内存管理上的混乱。 2、OC在内存管理方面的完善OC中引入了计数器和所有权的概念。对象除了有自己的成员和方法外,还从NSObject类继承了一个保留计数器,又称引用计数器(retainCount)。每一个OC对象都有一个4个字节的retainCount的计数器,表示当前对象被引用的计数。如果对象的计数变为0时,系统就会调用对象的dealloc方法,真正释放这个对象。 3、引用计数器的使用引用计数器工作机制: (1)对象知道自己当前被引用的次数。 (2)最初创建对象时,对象的计数器为1。 (3)如果需要引用(持有)对象,可以给对象发送一个retain消息,对象的引用计数器加1。 (4)当不需要引用对象了,可以给对象发送release消息,这样对象的引用计数器就减1。 (5)当对象的引用计数器为1时,再给对象发送一条release消息,引用计数器减1,并自动调用对象的dealloc函数,销毁对象。 (6)计数器为0的对象不能再使用release或试图发送retain消息复活对象,那样会引起程序崩溃。 引用计数器的查看、增加引用和减少引用: Person *person1=[ [Person alloc] init];//创建一个Person对象,retainCount为1 NSLog(@”%ld”,[person1retainCount]);//1 Person *person2= [person1 retain];//person2指针和person1指向同一个对象,计数器+1 NSLog(@”%ld”,[person2retainCount]);//2 [person2release];//1 [person1release];//0,自动调用dealloc销毁对象 注意:只有通过alloc、new、copy方式创建的对象才有所有权;其他方式创建的指针想拥有所有权,需要发送retain消息获得所有权。那就意味着,若指向对象的指针有很多个,而拥有所有权的指针恰好等于引用计数器中的数字;只有拥有所有权的指针,才有资格使引用计数器做减1操作。 4、相关概念野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存)。 僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用(打开僵尸对象检测)。 空指针:没有指向任何东西的指针(存储的东西是0,null,nil),给空指针发送消息不会报错。 四、内存管理法则The basic ruleto apple is everything thatincreases the reference counter withalloc,[mutable]copy[WithZone:] or retainis in charge of the corresponding[auto]release. 如果一个对象使用了alloc、[mutable]copy、retain,那么你必须使用相应的release或autonrelease。 (1)只要还有人在使用某个对象,那么这个对象就不会被回收;只要你想使用这个对象,那么就应该让这个对象的引用计数器+1;当你不想使用这个对象时,应该让对象的引用计数器-1; (2)谁创建,谁负责release。如果你通过alloc,new,copy来创建了一个对象,那么你就必须调用release或者autorelease方法;不是你创建的就不用你去负责。 (3)谁retain,谁负责release。只要你调用了retain,无论这个对象时如何生成的,你都要调用release。 五、自动释放池的使用@autoreleasepool{ Person *person1=[[ [Person alloc] init]autorelease]; …… } 六、Autorelease 1、基本用法(1)autorelease会将对象放到一个自动释放池中; (2)当自动释放池被销毁时,会对池子里的所有对象发送一条release;最后销毁自身。 注意:给所有对象发送一条release消息,并不是销毁对象。 自动释放池可以嵌套使用。自动释放池遵从栈式管理,在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的。当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中。 每当向对象发送一条autorelease消息时,就是将其放到最近的一个自动释放池。加入到autorelease中的对象,不需要手动发送release。 2、自动释放池的优劣使用自动释放池的好处是: (1)不需要再关心对象释放的时间; (2)不需要再关心什么时候调用release。 注意,创建对象时发送了autorelease之后,就不能再对对象发送release消息。 自动释放池的劣势是:自动释放池具有延迟性,只有到达结束边界时,才会给其中的所有对象发送release消息。占用内存较大的对象,不要随便使用autorelease,应该使用release来精确控制。 3、自动释放池的创建方式(1)ios 5.0以前的创建方式 NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init]; ````````````````` [pool release];//[pool drain];用于mac (2)Ios5.0以后 @autoreleasepool {//开始代表创建自动释放池 ······· }//结束代表销毁自动释放池 4、Autorelease注意(1)系统自带的方法中,如果不包含alloc new copy等,则这些方法返回的对象都是autorelease的,如[NSDate date]; (2)开发中经常会写一些类方法来快速创建一个autorelease对象,创建对象时不要直接使用类名,而是使用self。 七、常见的属性关键字1、常见属性关键字OC中属性声明如下: @property (nonatomic, assign) @property (nonatomic, retain) 可以看到关键字@property后的括号出现了四个特征性关键字:nonatomic , assign , retain , settet, 常见的属性关键字如下:
关于nonatomic,如果我们能确定不需要多线程访问时,强烈推荐使用这个关键字,因为atomic对于性能的损失相对较大。 如果是类的delegate,推荐使用assign关键字,原因是避免了retain的死循环造成的对象无法真正的释放。 2、ARC新增的属性关键字ARC新增两个属性关键字:strong 八、举例说明属性与内存管理比如有一个引擎类Engine,有一个汽车类Car,Car里面有一个Engine的实例变量,一个setter和getter方法。代码如下: #import"Car.h" @implementationCar //setter -(void)setEngine:(Engine*)engine { _engine=engine; } //getter -(Engine*)engine { return _engine; } //dealloc -(void)dealloc { NSLog(@"Car is dealloc"); [super dealloc]; } @end 第一步改进: 使用以上定义的类,看问题在哪。在main方法里调用如下: //创建一个引擎对象 Engine*engine1=[[Engine alloc]init]; [engine1 setID:1]; //创建一个汽车对象,并安装引擎 Car* car=[[Car alloc]init];//retainCount=1 [carsetEngine:engine1]; [engin1 release];//错误!car的引擎将为空,车子被卸掉引擎了! 问题分析: 代码中,有两个引用指向这个Engine对象,engine1和Car中的_engine,可是这个Engine对象的引用计数还为1,因为在set方法中,并没有使用retain。那么不管是哪个引用调用release,那么另外一个引用都会指向一块释放掉的内存,那么肯定会发生错误。 第二步改进: //setter方法改进 -(void)setEngine:(Engine*)engine { _engine=[engine retain];//多了一个引用,retainCount+1 } 在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方法 [carsetEngine:engine2]; 问题分析: 代码中,汽车换了一个引擎,那么它的_engine就不再指向engine1的哪个对象的内存了,而是换成了engine2,也就是说指向engine1对象的指针只有一个,而对象本身的retainCount是2,显然内存泄露了。 第三步改进: -(void)setEngine:(Engine*) engine { //在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉 [_engine release]; _engine=[engine retain];//多了一个引用,retainCount+1 } 第四步改进: -(void)setEngine:(Engine*) engine { //判断是否重复设置,以防调用时重复赋值 if(_engine!=engine){ //在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉 [_engine release]; _engine=[engineretain];//多了一个引用,retainCount+1 } } 第五步改进: 现在setter方法基本没有问题了,那么在当我们要释放掉一个car对象的时候,必须也要释放它里面的_engine的引用,所以,要重写car的dealloc方法: -(void)dealloc { [_engine release]; //在释放car的时候,释放掉它对engine的引用 [super dealloc]; } 以上操作看似没问题,但误操作时会向僵尸对象发送消息引起程序崩溃,所以还不是最好的释放的方法,下面的方法更好: -(void)dealloc { //在释放car的时候,对setEngine设置为nil,它不仅会release掉,并且指向nil,即使误操作调用也不会出错 [_engine setEngine:nil]; [super dealloc]; } 所以,综上所述,在setter方法中的最终写法是: <spanstyle="color:#CC66CC;">-(void)setEngine:(Engine*) engine { if(_engine!=engine){ [_engine release]; _engine=[engine retain]; } } 然后在dealloc方法中写法是: -(void)dealloc { [_engine setEngine:nil]; [super dealloc]; } 八、property中的setter语法关键字在property属性中有3个关键字定义关于展开setter方法中的语法,assgin(缺省),retain,copy。当然这三个关键字是互斥的。 1、assgin展开stter的写法-(void)setEngine:(Engine*) engine { _engine=engine; } 2、retain展开的写法-(void)setEngine:(Engine*) engine { if(_engine!=engine){ [_enginerelease]; _engine=[engineretain]; } } 可以看到,使用retain和我们上面举得例子完全相同,所以我们可以使用property和它的retain代替之前的写法。 3、copy展开的写法-(void)setEngine:(Engine*) engine { if(_engine!=engine){ [_engine release]; _engine=[enginecopy]; } } 对于copy属性有一点要主要,被定义有copy属性的对象必须要符合NSCopying协议,并且你还必须实现了-(id)copyWithZone:(NSZone*)zone该方法。 九、ARC1、ARC的判断准则只要没有强指针指向对象,对象就会被释放。 2、指针分类(1)强指针:默认的情况下,所有的指针都是强指针,关键字strong; (2)弱指针:_ _weak关键字修饰的指针。 声明一个弱指针如下: _ _weak Person*p; ARC中,只要弱指针指向的对象不在了,就直接把弱指针做清空操作。 _ _weak Person*p=[[Person alloc] init];//不合理,对象一创建出来就被释放掉,对象释放掉后,ARC把指针自动清零。 ARC中在property处不再使用retain,而是使用strong,在dealloc中不需要再[superdealloc]。 @property(nonatomic,strong)Dog *dog;// 如果换成是弱指针,则换成weak,不需要加_ _。 3、ARC的特点(1)不允许调用release,retain,retainCount。 (2)允许重写dealloc,但是不允许调用[superdealloc]。重写时全局指针都置nil。 (3)@property的参数 strong:相当于原来的retain(适用于OC对象类型),成员变量是强指针。 weak:相当于原来的assign(适用于OC对象类型),成员变量是弱指针。 |