iOS开发中的内存管理

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

系统资源有限,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)
int B;

@property (nonatomic, retain)
id classObj;

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

常见的属性关键字如下:


属性关键字


使用范围


含义


是否默认值


备注


assign


赋值方式


不复制不保留,直接赋值


YES


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


retain


赋值方式


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


NO


大部分对象可用


copy


赋值方式


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


NO


字符串选择性使用


readwrite


读写权限


生成getter和setter两个方法


YES


变量可读取可修改


readonly


读写权限


只生成getter方法


NO


变量只读不可修改


atomic


原子性


原子操作


YES


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


nonatomic


原子性


非原子操作


NO


不生成多线程同步内容


getter


存取方法


自定义取方法


NO

 

setter


存取方法


自定义赋值方法


NO

 

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

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

2、ARC新增的属性关键字

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

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

比如有一个引擎类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该方法。

九、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]。

@property(nonatomic,strong)Dog *dog;//
意味着生成的成员变量_dog是一个强指针,相当于以前的retain。

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

3、ARC的特点

(1)不允许调用release,retain,retainCount。

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

(3)@property的参数

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

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

时间: 2024-10-12 16:34:46

iOS开发中的内存管理的相关文章

iOS开发中的内存分配与分区

iOS开发中的内存分配与分区 关于RAM&ROM RAM与ROM就是具体的存储空间,统称为存储器. RAM(random access memory):运行内存,CPU可以直接访问,读写速度非常快,但是不能掉电存储.它又分为: 动态DRAM,速度慢一点,需要定期的刷新(充电),我们常说的内存条就是指它,价格会稍低一点,手机中的运行内存也是指它. 静态SRAM,速度快,我们常说的一级缓存,二级缓存就是指它,当然价格高一点. ROM(read only memory):存储性内存,可以掉电存储,例如

Cocos2d-x开发中C++内存管理

由于开始并没有介绍C++语言,C++的内存管理当然也没进行任何的说明,为了掌握Cocos2d-x中的内存管理机制,是有必要先了解一些C++内存管理的知识.C++内存管理非常复杂,如果完全地系统地介绍可能需要一本书的篇幅才能解释清楚.这里只给大家介绍C++内存管理最为基本的用法. 内存分配区域创建对象需要两个步骤:第一步,为对象分配内存,第二步,调用构造函数初始化内存.在第一步中对象分配内存时候,我们可以选择几个不同的分配区域,这几个区域如下:栈区域分配.栈内存分配运算内置于处理器的指令集中,效率

Unity游戏开发中的内存管理_资料

内存是手游的硬伤——Unity游戏Mono内存管理及泄漏http://wetest.qq.com/lab/view/135.html 深入浅出再谈Unity内存泄漏http://wetest.qq.com/lab/view/150.html 这一次,我优化了37%的内存http://wetest.qq.com/lab/view/147.html Unity项目资源加载与管理http://wetest.qq.com/lab/view/124.html Android应用内存泄露分析.改善经验总结h

Cocos2d-x开发中Ref内存管理

Ref类是Cocos2d-x根类,Cocos2d-x中的很多类都派生自它,例如,我们熟悉的节点类Node也派生自Ref.我们介绍Ref内存管理.内存引用计数Ref类设计来源于Cocos2d-iphone的CCObject类,在Cocos2d-x 2.x中也叫CCObject类.因此Ref类的内存管理是参考Objective-C手动管理引用计数(Reference Count)而设计的.如图所示是内存引用计数原理示意图. 每个Ref对象都有一个内部计数器,这个计数器跟踪对象的引用次数,被称为"引用

iOS开发中的内存分配(堆和栈)

进程的内存分区 所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的. 进程内存区域.png 代码区:代码段是用来存放可执行文件的操作指令(存放函数的二进制代码),也就是说是它是可执行程序在内存种的镜像.代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作--它是不可写的. 全局(静态)

[转载]对iOS开发中内存管理的一点总结与理解

对iOS开发中内存管理的一点总结与理解 做iOS开发也已经有两年的时间,觉得有必要沉下心去整理一些东西了,特别是一些基础的东西,虽然现在有ARC这种东西,但是我一直也没有去用过,个人觉得对内存操作的理解是衡量一个程序员成熟与否的一个标准.好了,闲话不说,下面进入正题. 众所周知,ObjectiveC的内存管理引用的一种叫做“引用计数“ (Reference Count)的操作方式,简单的理解就是系统为每一个创建出来的对象,(这里要注意,只是对象,NSObject的子类,基本类型没有‘引用计数’)

iOS开发中的ARC内存管理de技术要点

本文旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇文章不是一篇标准的ARC使用教程,并假定读者已经对ARC有了一定了解和使用经验.详细的关于ARC的信息请参见苹果的官方文档与网上的其他教程:) 本文的主要内容: ARC的本质 ARC的开启与关闭 ARC的修饰符 ARC与Block ARC与Toll-Free Bridging ARC的本质 ARC是编译器(时)特性,而不是运行时

IOS阶段学习第20天笔记(OC中的内存管理)

IOS学习(OC语言)知识点整理 一.OC中的内存管理 1)概念:内存管理的对象为所有继承了NSObject的对象,对基本数据(如:int .float.double...)无效      OC中采用引用计数器对内存做管理,他是一个整数数据,表示对象引用的次数,每个对象分配4字节      的内存空间存放引用计数器.当一个对象的引用计数器为0时 它将被自动释放,反过来说 当使用alloc.      new .copy(mutableCopy)创建新对象时,引用计数器默认为1 2)黄金法则 当使

iOS学习第四天杂记--Objective-C中的内存管理

先说明下,原文为青玉伏案写的.我这只是学习而已. OC中的内存管理. OC中使用引用计数和垃圾回收来管理内存,在OC中为每个对象分配一个引用计数器,当对象刚刚被创建时其初始值为1,当有某段代码需要访问一个对象时,会将该对象的引用计数器加1(通过retain来实现):当访问一个对象结束时,会将该对象的引用计数器减1(通过release来实现):当计数器为0时,该对象占用的内存空间会被收回.在NSObject类有一个retainCount方法,调用该方法可获取当前对象的引用计数值. Tips:测试时