iOS开发中的内存管理 九、ARC

iOS开发中的内存管理
九、ARC


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

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

二、须要管理的内存

应用程序在执行过程中。会占用一定栈空间和堆空间,也就是说。应用程序执行过程中的数据,有的是放在栈中,有的是放在堆中。

栈中的数据由系统维护,无需开发者来管理,而堆中的数据须要程序猿来维护。

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

在这里须要注意两点:

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

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

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

1CC++内存管理的不足

CC++中,若有3个指针指向同一个对象,不论什么一个指针调用了free方法释放内存,其余的引用在不知道的情况下继续使用这块内存的时候,就会出现故障。

因此,何时由谁去释放这块内存,这就是CC++在内存管理上的混乱。

iOS开发中的内存管理
九、ARC

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

OC中引入了计数器和全部权的概念。

对象除了有自己的成员和方法外。还从NSObject类继承了一个保留计数器,又称引用计数器(retainCount)。每个OC对象都有一个4个字节的retainCount的计数器,表示当前对象被引用的计数。假设对象的计数变为0时,系统就会调用对象的dealloc方法,真正释放这个对象。

iOS开发中的内存管理
九、ARC

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

七、常见的属性keyword

1、常见属性keyword

OC中属性声明例如以下:

@property (nonatomic, assign) int B;

@property (nonatomic, retain) id classObj;

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

常见的属性keyword例如以下:

属性keyword

使用范围

含义

是否默认值

备注

assign

赋值方式

不复制不保留,直接赋值

YES

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

retain

赋值方式

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

NO

大部分对象可用

copy

赋值方式

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

NO

字符串选择性使用

readwrite

读写权限

生成gettersetter两个方法

YES

变量可读取可改动

readonly

读写权限

仅仅生成getter方法

NO

变量仅仅读不可改动

atomic

原子性

原子操作

YES

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

nonatomic

原子性

非原子操作

NO

不生成多线程同步内容

getter

存取方法

自己定义取方法

NO

 

setter

存取方法

自己定义赋值方法

NO

 

关于nonatomic,假设我们能确定不须要多线程訪问时,强烈推荐使用这个keyword,由于atomic对于性能的损失相对较大。

假设是类的delegate,推荐使用assignkeyword。原因是避免了retain的死循环造成的对象无法真正的释放。

2ARC新增的属性keyword

  ARC新增两个属性keyword: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语法keyword

property属性中有3个keyword定义关于展开setter方法中的语法。assgin(缺省)retaincopy

当然这三个keyword是相互排斥的。

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该方法。

1、ARC的推断准则

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

2、指针分类

1)强指针:默认的情况下,全部的指针都是强指针,keywordstrong

2)弱指针:_ _weakkeyword修饰的指针。

声明一个弱指针例如以下:

_ _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对象类型),成员变量是弱指针。