iOS开发中的内存管理

来源:互联网 发布:搬家软件app 编辑:程序博客网 时间:2024/05/01 19:57


一、为什么要进行内存管理

系统资源有限,iOS会为每个运行的程序分配30M的内存,超过20M会收到内存警告,超过30M将会终止应用程序。因此,要及时回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等,以保证应用程序能正常运行。

二、需要管理的内存

应用程序在运行过程中,会占用一定栈空间和堆空间,也就是说,应用程序运行过程中的数据,有的是放在栈中,有的是放在堆中。栈中的数据由系统维护,无需开发人员来管理,而堆中的数据需要程序员来维护。

堆空间由开发人员请求分配的,比如开发人员发送一条alloc消息创建一个对象,实际上就向堆空间申请了一块内存,用于存储创建的对象。对象存储于堆中,当代码块结束时,这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,若对象依然存在于内存中,就会造成内存泄露。

在这里需要注意两点:

1)基本数据类型一般放在栈中,不需要进行内存管理;

2)创建对象时,指向对象的指针放在栈中,由系统维护,而指针指向的对象,则是放在堆中,需要开发人员维护。

三、对象的结构与内存管理机制

1CC++内存管理的不足

CC++中,若有3个指针指向同一个对象,任何一个指针调用了free方法释放内存,其余的引用在不知道的情况下继续使用这块内存的时候,就会出现问题。因此,何时由谁去释放这块内存,这就是CC++在内存管理上的混乱。


2OC在内存管理方面的完善

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对象,retainCount1

NSLog(@”%ld”,[person1retainCount]);//1

Person *person2= [person1 retain];//person2指针和person1指向同一个对象,计数器+1

NSLog(@”%ld”,[person2retainCount]);//2

[person2release];//1

[person1release];//0,自动调用dealloc销毁对象

 

注意:只有通过allocnewcopy方式创建的对象才有所有权;其他方式创建的指针想拥有所有权,需要发送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]copyretain,那么你必须使用相应的releaseautonrelease

1)只要还有人在使用某个对象,那么这个对象就不会被回收;只要你想使用这个对象,那么就应该让这个对象的引用计数器+1;当你不想使用这个对象时,应该让对象的引用计数器-1

2)谁创建,谁负责release。如果你通过alloc,new,copy来创建了一个对象,那么你就必须调用release或者autorelease方法;不是你创建的就不用你去负责。

3)谁retain,谁负责release。只要你调用了retain,无论这个对象时如何生成的,你都要调用release

五、自动释放池的使用

@autoreleasepool{

Person *person1=[[ [Person alloc] init]autorelease];

……

}

 

六、Autorelease

1、基本用法

1autorelease会将对象放到一个自动释放池中;

2)当自动释放池被销毁时,会对池子里的所有对象发送一条release;最后销毁自身。

注意:给所有对象发送一条release消息,并不是销毁对象。

自动释放池可以嵌套使用。自动释放池遵从栈式管理,在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的。当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中。

每当向对象发送一条autorelease消息时,就是将其放到最近的一个自动释放池。加入到autorelease中的对象,不需要手动发送release

2、自动释放池的优劣

使用自动释放池的好处是:

1)不需要再关心对象释放的时间;

2)不需要再关心什么时候调用release

注意,创建对象时发送了autorelease之后,就不能再对对象发送release消息。

自动释放池的劣势是:自动释放池具有延迟性,只有到达结束边界时,才会给其中的所有对象发送release消息。占用内存较大的对象,不要随便使用autorelease,应该使用release来精确控制。

3、自动释放池的创建方式

1ios 5.0以前的创建方式

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

`````````````````

 [pool release];//[pool drain];用于mac

2Ios5.0以后

@autoreleasepool

{//开始代表创建自动释放池

·······

}//结束代表销毁自动释放池

4Autorelease注意

1)系统自带的方法中,如果不包含alloc new copy等,则这些方法返回的对象都是autorelease的,如[NSDate  date]

2)开发中经常会写一些类方法来快速创建一个autorelease对象,创建对象时不要直接使用类名,而是使用self

七、常见的属性关键字

1、常见属性关键字

OC中属性声明如下:

@property (nonatomic, assign)int B;

@property (nonatomic, retain)id classObj;

可以看到关键字@property后的括号出现了四个特征性关键字:nonatomic , assign , retain , settet,这些关键字直接告诉编译器后面的变量用何种方式来存取。

常见的属性关键字如下:

属性关键字

使用范围

含义

是否默认值

备注

assign

赋值方式

不复制不保留,直接赋值

YES

基本数据类型和本类不直接拥有的对象

retain

赋值方式

将新值保留一份,覆盖原值

NO

大部分对象可用

copy

赋值方式

将新值复制一份赋覆盖原值

NO

字符串选择性使用

readwrite

读写权限

生成gettersetter两个方法

YES

变量可读取可修改

readonly

读写权限

只生成getter方法

NO

变量只读不可修改

atomic

原子性

原子操作

YES

可以保留在多线程环境下,能安全的存取值

nonatomic

原子性

非原子操作

NO

不生成多线程同步内容

getter

存取方法

自定义取方法

NO

 

setter

存取方法

自定义赋值方法

NO

 

关于nonatomic,如果我们能确定不需要多线程访问时,强烈推荐使用这个关键字,因为atomic对于性能的损失相对较大。

如果是类的delegate,推荐使用assign关键字,原因是避免了retain的死循环造成的对象无法真正的释放。

2ARC新增的属性关键字

  ARC新增两个属性关键字:strong weakstrong的含义和retain相同,weakassign相同,修饰完的属性变量用法也是完全没有改变,不过strongweak只能修饰对象。

八、举例说明属性与内存管理

比如有一个引擎类Engine,有一个汽车类CarCar里面有一个Engine的实例变量,一个settergetter方法。代码如下:

#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对象,engine1Car中的_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对象的指针只有一个,而对象本身的retainCount2,显然内存泄露了。

 

第三步改进:

-(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的引用,所以,要重写cardealloc方法:

-(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(缺省)retaincopy。当然这三个关键字是互斥的。

1assgin展开stter的写法

-(void)setEngine:(Engine*) engine

{

    _engine=engine;

}

2retain展开的写法

-(void)setEngine:(Engine*) engine

{

  if(_engine!=engine){

[_enginerelease];

    _engine=[engineretain];

     }

}

可以看到,使用retain和我们上面举得例子完全相同,所以我们可以使用property和它的retain代替之前的写法。

3copy展开的写法

-(void)setEngine:(Engine*) engine

{

  if(_engine!=engine){

 [_engine release];

     _engine=[enginecopy];

     }

}

对于copy属性有一点要主要,被定义有copy属性的对象必须要符合NSCopying协议,并且你还必须实现了-(id)copyWithZone:(NSZone*)zone该方法。

九、ARC

1、ARC的判断准则

只要没有强指针指向对象,对象就会被释放。

2、指针分类

1)强指针:默认的情况下,所有的指针都是强指针,关键字strong

2)弱指针:_ _weak关键字修饰的指针。

声明一个弱指针如下:

_ _weak Person*p;

ARC中,只要弱指针指向的对象不在了,就直接把弱指针做清空操作。

_ _weak Person*p=[[Person alloc]  init];//不合理,对象一创建出来就被释放掉,对象释放掉后,ARC把指针自动清零。

ARC中在property处不再使用retain,而是使用strong,在dealloc中不需要再[superdealloc]

@propertynonatomic,strongDog *dog;//意味着生成的成员变量_dog是一个强指针,相当于以前的retain

如果换成是弱指针,则换成weak,不需要加_ _

3、ARC的特点

1)不允许调用releaseretainretainCount

2)允许重写dealloc,但是不允许调用[superdealloc]。重写时全局指针都置nil

3@property的参数

strong:相当于原来的retain(适用于OC对象类型),成员变量是强指针。

weak:相当于原来的assign(适用于OC对象类型),成员变量是弱指针。



0 0