您的位置:首页 > 移动开发 > IOS开发

IOS内存原理知识介绍

2013-11-10 20:53 239 查看
1 概要
由于在测试过程中手机的测试项目越来越多,作者要学习一下IOS环境手机开发的平台的一些相关知识,方便测试代码走读。当测试过程中遇到一些内存泄漏等问题时,尽快知道是什么原因导致的bug。通过学习之后把这些原理知识整理出来,来方便以后的阅读和学习。
要介绍IOS内存管理机制,首先我们先了解一下Objective-C 提供了内存管理方式另外iOS下内存管理的基本思想就是引用计数,通过对象的引用计数来对内存对象的生命周期进行控制。主要是MRR和ARC两种方式。本文主要介绍MRR方式。

2 关于Objective-C内存管理

应用程序的内存管理是一个在程序运行时进行内存分配,使用内存、结束时释放内存的过程。书写良好的程序,会尽可能少占用内存。在Objectiv-C 中,这个过程也是一个在很多片代码或者数据中传播有限内存资源的“所有权”(Ownership)的方式。

2.1 内存管理方式简介

Objective-C 提供了三种内存管理方式:
1,本文中将要讲述的一种方式,称为“手工持有-释放”(Manual
Retain-Release)或MRR。你通过跟踪你所拥有的对象来“显式地”管理内存。这种方式采用了一种称为“引用计数”的模型。该模型由基础类NSObject和运行时(Runtime
Environment)共同提供。

2,自动引用计数(Automatic Reference Counting),或ARC,的方式。系统采用和MRR 相同的引用计数系统,但是在编译时(Compile-time)插入了内存管理的方法。对于新的工程项目,强烈建议使用ARC 方式,这样你不需要理解本文所述的底层实现。不过,个别情况下,你会受益于对这些底层实现的理解。ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存;更多关于ARC的讲述,请参看Transitioning
To ARC Release Notes。

3,垃圾回收的方式。系统自动跟踪对象跟对象之间的引用关系。对于没有引用的对象,自动进行回收。这种机制和前面说的MRR 和ARC 都不同,且只能在Mac
OS 下使用,iOS 下是不行的。更多关于垃圾回收机制的内容,请参看 Garbage Collection Programming Guide。

说明:
MRR手动管理内存也是基于引用计数的,只是需要开发者发消息给某块内存(或者说是对象)来改变这块内存的引用计数以实现内存管理(ARC技术则是编译器代替开发者完成相应的工作)。一块内存如果计数是零,也就是没有使用者(owner),那么objective-C的运行环境会自动回收这块内存。

2.2 内存管理遵循策略简介

objective-C的内存管理遵守下面这个简单的策略:

通常我们把引用计数加1的操作称为“拥有”(own,或者take
ownership of)某块对象/内存;把引用计数减1的操作称为放弃(relinquish)这块对象/内存。拥有对象时,你可以放心地读写或者返回对象;当对象被所有人放弃时,objective-C的运行环境会回收这个对象。objective-C的内存管理遵守策略如下:

1.你拥有你创建的对象,也就是说创建的对象(使用alloc,new,copy或者mutalbeCopy等方法)的初始引用计数是1。

2.给对象发送retain消息后,你拥有了这个对象;

3.当你不需要使用该对象时,发送release或者autorelease消息放弃这个对象;

4.不要对你不拥有的对象发送“放弃”的消息;

所有权机制:
1,在OC中,对象不断的被其它创建、使用和销毁,为了保证程序不产生额外的内存开销,当对象不再被需要以后,应当被立即销毁。
2,拥有对一个对象的使用权,我们称为是”拥有”这个对象。对象的拥有者个数至少为1,对象才得以存在,否则它应当被立即销毁。
3,为了确定你(一个对象)何时具有对另外一个对象的所有权,以及明确对象所有者的权利和义务,Cocoa设立了一套标准。只有当你对一个对象做了alloc,copy和retain等操作之后,你才拥有它的所有权。当你不再需要这个对象的时候,你应该释放你对它的所有权。
千万不要对你没有所有权的对象进行释放。

2.3 常见错误的内存管理

错误的内存管理往往包括两类:
1,释放(free)或者覆盖(over-write)正在使用中的数据:造成内存异常,导致应用程序
崩溃,甚至导致数据损坏。

2,不用的数据却不释放,从而导致内存泄露。
内存泄露,就是有内存分配但是不释放它,哪怕这块内存已经不用了。泄露,导致你的应用程序占用越来越多的内存,并导致整体性能的下降,或者在iOS平台上导致应用终止。

如果你总是考虑内存管理的实现细节,而不是你实际的管理目标,那么你会感觉到从“引用计数”的角度理解内存管理实际是极其困难的。所以,你真正应该考虑的是对象的“所有权”(Ownership)以及对象图(Object
Graph)。

当一个方法所返回的对象,其所有权属于你的时候, Cocoa 用一种非常直接的命名规范来告诉你。请参看内存管理策略;尽管最基础的策略也是最直接的,有一些有效的做法可以让内存管理更加容易,从而帮助你实现程序的稳定性和健壮性,从而使其占用更少的资源。请参看内存管理实战;自动释放池(Autorelease
Pool)使得你可以用一种不同的方式来发送release消息。当你想放弃对一个对象的所有权,但又不想让这个所有权的释放立即生效(比如,你在方法中要返回这个对象),这种机制就很有用了。有几种情况你应该需要使用自动释放池。请参看使用Autorelease池。

3 IOS内存管理基础介绍

是否曾经为内存问题而苦恼过?内存莫名的持续增长,程序莫名的 crash,难以发现的内存泄漏,这些都是iOS平台内存相关的常见问题;这里将详细介绍iOS平台的内存管理机制,autorelease机制和内存的使用陷阱,这些将会解决iOS平台内存上的大部分问题,提高了程序的稳定性;

3.1 IOS内存管理方式简介

iOS下内存管理的基本思想就是引用计数,通过对象的引用计数来对内存对象的生命周期进行控制。主要是MRR和ARC两种方式,前面介绍过。
1, MRR(manual retain-release),人工引用计数,对象的生成、销毁、引用计数的变化都是由开发人员来完成。

2, ARC(Automatic Reference Counting),自动引用计数,只负责对象的生成,其他过程开发人员不再需要关心其销毁,使用方式类似于垃圾回收,但其实质还是引用计数。
说明:iOS不支持垃圾回收机制,这点与Mac OS有所不同。

ARC是Xcode 4.2之后加入的新特性,可能很多开发人员并不习惯使用,但使用ARC给开发带来的便利是显而易见的,鼓励大家都去尝试一下。ARC的具体介绍及使用细则大家可以参考苹果官方文档苹果官方文档链接:https://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226

iOS需要开发者管理内存,很高高级语言都有垃圾回收机制,但iOS开发却没有垃圾回收机制。虽然iOS没有垃圾回收机制,但是mac
OS有垃圾回收机制。iOS将内存管理的任务交给了开发者。

3.2 IOS内存原理介绍

iOS平台的内存使用引用计数的机制,并且引入了半自动释放机制;这种使用上的多样性,导致开发者在内存使用上非常容易出现内存泄漏和内存莫名的增长情况;下面会介绍iOS平台的内存使用原则与使用陷阱; 深度剖析autorelease机制;低内存报警后的处理流程;并结合自身实例介绍内存暴增的问题追查记录以及相关工具的使用情况;

MRR内存管理里的一个核心原则,“只负责你拥有的对象的生命周期”,也就是说,如果你对一个对象有所有权,那么你就要负责其回收的工作,否则,你不需要,也不能取回收你不拥有的对象。
哪些对象属于“拥有”范畴呢?
1:所有使用alloc, new, copy或mutabelCopy,以及这些关键词开头的函数返回的对象,你都是拥有所有权的,也就是要负责这些对象的内存回收工作。这是iOS开发中的一种约定,所以,当你编写自己的alloc,
new或copy类型的函数时,也请遵循这样的命名规范。
2:retain返回的对象,拥有所有权。例如显示调用retain函数返回的结果,或者synthesize 的retain类型的成员变量。
3:所有使用其他函数返回的对象,没有所有权。
4:返回的对象的引用,没有所有权。
5:autorelease返回的对象没有所有权。

实例说明如下:



3.2.1 成员变量的内存管理

注意上面的例子4,将成员变量声明为retain并synthesize时,编译器会帮你生成对应变量的setter与getter,其中, 生成的setter形式如下:

[cpp] view
plaincopy

1 - (void) setStringD: (NSString*) newString
2 {
3 [newString retain];
4 [_stringD release];
5 _stringD = newString;
6 }

首先获得newString的所有权,然后释放掉已经拥有的_stringD(其实此对象有可能并没有被释放,取决与其release后的引用计数是否为0), 再将newString的值赋给_stringD,这样,获得了新的,释放了旧的,就完成了对象所有权的释放与获得。注意retain与release的调用顺序,避免了同一个对象赋值引起的悬空指针问题。当成员变量声明为assign和copy时,其生成的setter形式如下:



[cpp] view
plaincopy

copy类型的成员变量也是拥有所有权的,也要在dealloc函数中显式释放。而assign不是。很多人对self.stringD
= @"abc"的调用形式比较困惑; 其实,编译器会将此语句自动转换为[self.setStringD:@"abc"];
还有一点要注意,声明成员类型时只有copy,没有mutableCopy,那么,如果一个成员变量是MutableArray,且被声明成copy类型,但编译器生成的setter中调用的是copy,而不是MutableCopy,所以,向MutableArray类型的成员插入数据时就会报错,因为其保存的真正类型不是MutableArray,而是Array. 这种情况有两个解决方案:一是将改变量类型声明为retain形式; 二是手动编写setter,调用mutableCopy函数。

NSObject函数声明了dealloc函数来清理内存,所有有“retain, copy”类型成员变量的类都要实现这个函数。在其内,要调用相应对象的release函数,不要忘记在【最后】调用[super
dealloc];
说到成员变量的声明周期,还要提一下IBOutlet类型的变量,默认情况下, IBOutlet对象的类型都是retain的,由于这些对象来自界面文件(xib,
storyboard),所以其出事化过程无需关心,其他处理方式与普通成员变量大体相同,只有一点,需要在- (void) viewDidUnload函数中将这些变量置空, 如下所示:

[cpp] view plaincopy

7 - (void) viewDidUnload
8 {
9 self.outletA = nil;
10 self.outletB = nil;
11 [super viewDidUnload];
12 }

当出现内存警告时,viewDidUnload将会被调用。前面提到过,self.outletA = nil 等价于[self
setOutletA: nil], 所以,在出现内存警告的情况下,IBOutlet类型的对象会被释放。

3.2.2 容器对象与内存管理

iOS中,容器对象对其内的对象拥有所有权,也就是说,当一个对象被插入到容器内时,其retainCount会加一,从容器内移除时,其retainCount会减一,当容器本身被release时,期内所有对象的retainCount都会减一。如下代码所示:

[cpp] view plaincopy

1 NSString* stringA = [[NSString alloc] init];//stringA的retainCount:
1
2 NSArray* array = [[NSArray alloc] init];
3 [array addObject: stringA];//stringA的retainCount:2
4 [stringA release];//stringA的retainCount:1
5 [array release];//retainCount: 0

3.2.3 稀缺资源的管理

稀缺资源包括文件,网络连接,缓存等,这些资源是很关键的系统资源,系统内其他应用可能会随时需要这些资源,所以,这些资源就不适合作为类的成员变量了,因为dealloc的实际调用时间,是否真正调用是我们无法控制的,很有可能造成稀缺资源被无意义的占用,二其他应用却无法获得相应资源。所以,随时申请随时释放是最好的选择。

3.2.4 AutoRelease

简单说,autorelease对象的释放动作由AutoReleasePool完成,所有autorelease对象在其【对应】的AutoReleasePool释放的过程中,都会受到一条release消息,也就是说,pool析构的实际也就是autorelease对象析构的时机,注意,这里的【对应】指的是离改autorelese最近的那一个AutoRelasePool。
有这么几种情况必须自己创建AutoReleasePool:
1:程序没有界面,也就是没有消息循环的程序,
2:一个循环内创建大量临时的autorelease对象,那么写法最好是这样的:

[cpp] view plaincopy

1 NSAutoReleasePool* outPool = [[NSAutoReleasePool alloc] init];
2 for (int index = 0; index != 1000000; ++index) {
3 NSAutoReleasePool* pool = [[NSAutoReleasePool alloc] init];
4 NSString* temp = [[[NSString alloc] init] autorelease];
5 [pool drain];
6 }
7 [outPool release];

若果没有循环中的pool, 那么直到结束循环之前,这1000000个autorelease 临时对象都不会被释放掉,占用大量内存。
3:线程函数内需要有AutoReleasePool对象,否则期内生成的autorelease对象在线程函数结束时不会被释放(此条对Cocoa-Touch不适用)。
4:普通函数返回对象,这里的普通函数指的是非alloc, new, copy, mutablecopy开头的函数,为了确保返回的对象有效,需要将返回的对象设为autorelease,如下所示:

[cpp] view
plaincopy

8 - (NSString*) returnString
9 {
10 NSString* tempString = [[NSString alloc] init];
11 tempString = @"autorelease";
12 return [tempString autorelease];
13 }

实际上,iOS SDK中绝大多数普通函数返回的都是autorelease对象。

3.2.5 其他注意事项

1:避免循环引用,如果两个对象互相为对方的成员变量,那么这两个对象一定不能同时为retain,否则,两个对象的dealloc函数形成死锁,两个对象都无法释放。
2:不要滥用autorelease,如果一个对象的生命周期很清晰,那最好在结束使用后马上调用release,过多的等待autorelease对象会给内存造成不必要的负担。
3:编码过程中,建议将内存相关的函数,如init, dealloc, viewdidload, viewdidunload等函数放在最前,这样比较显眼,忘记处理的概率也会降低。
4:AutoReleasePool对象在释放的过程中,在IOS下,release和drain没有区别。但为了一致性,还是推荐使用drain。

[cpp] view plaincopy

3.3 IOS平台内存管理介绍

iOS平台的内存管理采用引用计数的机制;当创建一个对象时使用alloc或者allWithZone方法时,引用计数就会+1;当释放对象使用release方法时,引用计数就是-1;这就意味着每一个对象都会跟踪有多少其他对象引用它,一旦引用计数为0,该对象的内存就会被释放掉;另外,iOS也提供了一种延时释放的机制 AutoRelease(也称为半自动释放机制),以这种方式申请的内存,开发者无需手动释放,系统会在某一时机释放该内存; 由于iOS平台的这种内存管理的多样性,导致开发者在内存使用上很容易出现内存泄漏或者程序莫名崩溃的情况;

3.3.1 完整示例介绍

下图是内存从申请到释放的一个完整示例:



3.3.2 方法描述

-retain:将一个对象的reference数量增加1。
-release:将一个对象的reference数量减少1。
-autorelease:在未来某些时候将reference数量减少1.
-alloc:为一个对象分配内存,并设置保留值数量(retain count)为1。
-copy:复制一个对象,并将其做为返回值。同时设置保留值数量(retain count)为1。

3.3.3 保留值数量规则

1 在一定的代码段中,使用-copy,-alloc和-retain的次数应该和-release,-autorelease保持一致。
例子:-alloc / -release
- (void)printHello
{
NSString *string;
string = [[NSString alloc] initWithString:@"Hello"];
NSLog(string);
// 我们用 alloc 创建了NSString,那么需要释放它
[string release];
}

2 使用便利构造方法创建的对象(比如NSString的stringWithString)可以被认为会被自动释放。(autoreleased)
便利构造方法
- (void)printHello
{
NSString *string;
string = [NSString stringWithFormat:@"Hello"];
NSLog(string);
// 我们用便利构造方法创建的NSString
//我们可以认为它会被自动释放
}

3 在使用你自己的参数实例时,需要实现-dealloc方法来释放。

3.3.4 便利构造器的管理

有时我们会通过便利构造器来获得一个新的对象,由便利构造器产生的对象不应当由使用者销毁,而是由便利构造器本身完成。
便利构造器通过在实现中调用autorelease来实现上述功能。

错误示例1
+(id) personWithName:(NSString *)aName
{
Person *person = [[Person alloc]
initWithName:aName];
return person;
}
它是错误的,因为它返回了新创建的对象以后,类就失去了释放这个对象的机会。

错误示例2
+(id) personWithName:(NSString *)aName
{
Person *person = [[Person alloc]
initWithName:aName];
[person release];
return person;
}
它也是错误的,因为在返回对象的时候,对象已经被销毁,从而不能使用了。

正确示例
+(id) personWithName:(NSString *)aName
{
Person *person = [[Person alloc]
initWithName:aName];
[person autorelease];
return person;
}
它是正确的,因为对新对象的销毁延迟进行,从而使用者有时间去使用它,而不必对对象的释放负责。

简单的使用示例
使用便利构造器创建对象,则使用完毕后不需要进行释放。
-(void) printHello
{
NSString *str = [NSString stringWithFormat:@”Hello”];
NSLog(@”%@”,str);
// 我们用便利构造方法创建的NSString
//我们可以认为它会被自动释放
}
4 IOS平台内存管理策略
通过引用计数来进行内存管理的基本模型是由NSObject 协议中的方法以及标准的方法命名规范来提供的。NSObject 还定义了一个dealloc方法,该方法会在对象需要析构的时候自动调用。本文描述了在cocoa 程序中正确管理内存的基本规则,并提供了正确的示例。

4.1 基本内存管理规则

内存管理的模型,是基于对象的“所有权”(ownership)的。任何一个对象都可以有一个或者更多的“所有者”(owner)。当一个对象有至少1个所有者的时候,该对象存在;没有了所有者时,系统自动析构它。为了更清晰描述你何时拥有一个对象、何时放弃所有权,Cocoa遵循下面的策略:

一,你拥有你所创建的对象:
你如果用下面字母作为开头的方法来创建对象,那么你将拥有这个对象:alloc、new、copy、mutableCopy。比如,alloc、newObject、或者mutableCopy 等方法。

二,你可以用retain来实现对一个对象的所有
如果你在一个方法体中,得到了一个对象,那么这个对象在本方法内部是一直都有效的。而且你还可以在本方法中将这个对象作为返回值返回给方法的调用者。在下面两种状况下,你需要用retain:(1)在访问方法(getter、setter)或者init 方法中,你希望将得到的返回对象作为成员变量(property)来存储。 (2)在执行某些操作时,你担心在过程中对象变得无效。(在 避免你正在使用的对象被dealloc 中详细解释。)

三,你不再需要一个对象时,你必须放弃对对象的持有
通过向对象发送release消息或者autorelease 消息来放弃所有权。用Cocoa 的术语说,所谓放弃所有权,就是release 一个对象。

四,对于你正在使用的对象,不要release它
这一点跟上一条对应,举例如下:
{
Person *aPerson =[[Person alloc] init];
NSString *name = person.fullName;
[aPerson release];
}
说明:这里的Person 对象是使用alloc 来初始化,然后用release 消息来释放的。Person 的name因为不是使用“所有的”方法(alloc, new, copy,…)来得到的,所以也不必release。请注意,这里用的是release而非autorelease。

4.2 延时release—使用autorelease

当你需要延时release方式时,就需要autorelease 了,特别是当你从方法中返回一个对象的时候。比如,你可以这样来实现fullName:

方法1:
-(NSString *) fullName{
NSString *string = [[[NSString alloc] initWithFormat:@”%@ %@”,
self.firstName, self.lastName] autorelease];
return string;
}
说明:你使用alloc 方法创建了string,所以你有该对象的所有权,因此你有义务在失去对
它的引用前放弃该所有权。但如果你用release,那么这种所有权的放弃是“即刻的”,立即生效。可是我们却要将这个对象return,这将造成return时对象已经实际失效,方法实际上返回了一个无效的对象。我们采用autorelease来声明(译者:注意这里仅仅是一种意愿的表达,而非实际放弃的动作。)我们对所有权的放弃,但是同时允许fullName: 方法的调用者来使用该对象。

方法2:
你还可以按下面做法来实现这个方法:
-(NSString *) fullName{
NSString *string = [NSString stringWithFormat:@”%@ %@”,
self.firstName,
self.lastName] ;
return string;
}
根据我们说的基本规则,你对stringWithFormat: 所返回的string 没有所有权(译者:请注
意到这里并没有使用alloc,方法名也不是以init 开始)。 因此你可以直接返回string 给方法的调用者。

方法3:错误的
而下面的做法就是错误的了:
-(NSString *) fullName{
NSString *string = [[NSString alloc] initWithFormat:@”%@ %@”,
self.firstName, self.lastName];
return string;
}
根据方法的命名规范,从这个方法的名字(fullName)看不出fullName 方法的调用者将拥有返回的对象(译者:因为它并没有以alloc、 new、 init 等开头)。因此,该方法的调用者自然也没有理由来release这个返回的string。这将最终导致内存泄露。

4.3 通过引用Reference来返回的对象,你没有所有权

在Cocoa 中,有些方法返回的对象是reference(即这些返回对象的类型是ClassName
** 或者 id *)。 常见的情况是当出现错误异常时,一个NSError 对象被用来承载错误的信息。比如initWithContentsOfUrl:options:error(NSData) 和
initWithContentsOfFile:encoding:error(NSString) (译者:注意这里的NSData 和NSString 都没有 * )。
这种情况下,我们前面说的规则依然有效:当你调用这类方法的时候,你没有创建NSError对象,因此你没有对它的所有权,也不必release 它。如下面所示(译者:注意error 不需要release):

NSString *fileName = <#Get a file name#>;
NSError * error = nil;
NSString *string =[[NSString alloc] initWithContentsOfFile:fileName
encoding:NSUTF8StringEncoding error:&error];
If(string == nil){
//Deal with error
}
// …
[string release];

4.4 实现对象的dealloc

NSObject类定义了一个名为dealloc的方法。这个方法在对象无主(没有所有者)的情况下,当内存回收的时候会由系统自动调用。用Cocoa 的术语讲,就叫free或者deallocate。Dealloc方法的作用就是释放对象的内存,并弃掉它持有的任何资源---以及它对其他对象的所有权。
下面的例子示范了你应该如何实现Person 类的dealloc方法:
@interface Person : NSObject {}
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (assign, readonly) NSString *fullName;
@end
@implementation Person
@synthesize firstName=_firstName, lastName=_lastName;
// ...
- (void)dealloc
[_firstName release];
[_lastName release];
[super dealloc];
}
@end
说明:
你永远不需要直接调用另一个对象的dealloc方法。
你必须在末尾调用super类的实现方法。
你不可以把系统的资源和对象的生命周期进行绑定。请参看 Don’t Use Dealloc To Manage
Scarce Resources。 (译者:就是说你不能等对象被dealloc 时才放弃对关键系统资源的独占。)因为进程的内存会在退出时自动回收,当应用退出时,对象可能收不到dealloc 这样的消息。和调用所有对象的内存管理方法的方式来比,操作系统的这种做法是高效率的。

4.5 Core Foundation 使用了类似但却不同的规则

Core Foundation 的对象采用了类似的内存管理方式(详细请参看:Memory Management Programming Guide for Core Foundation)。Cocoa 和Core
Foundation 在命名规则上是不同的。具体说,就是Core Foundatoin的创建规则,不适用于返回Objective-C对象的那些方法。比如下面的代码片段中,你没有责任或义务来释放对myInstance 的所有权:
MyClass *myInstance = [MyClass createInstance];

5 IOS平台内存报警机制

由于iOS平台的内存管理机制,不支持虚拟内存,所以在内存不足的情况,不会去Ram上创建虚拟内存;所以一旦出现内存不足的情况,iOS平台会通知所有已经运行的app,不论是前台app还是后台挂起的app,都会收到 memory
warning的notice;一旦app收到memory
warning的notice,就应该回收占用内存较大的变量;

5.1 内存报警处理流程

(1) app收到系统发过来的memory warning的notice;

(2) app释放占用较大的内存;

(3)系统回收此app所创建的autorelease的对象;

(4) app返回到已经打开的页面时,系统重新调用viewdidload方法,view重新加载页面数据;重新显示;

5.2 内存报警测试方法

在Simulate上可以模拟低内存报警消息;

iOS模拟器 -> 硬件 -> 模拟内存警告;

开发者可以在模拟器上来模拟手机上的低内存报警情况,可以避免由于低内存报警引出的app的莫名crash问题;

这里说明一下我们目前的APP一般都没有做这块的相应处理,所以目前没有测试;

6 IOS内存管理MRR技术

MRR内存管理里的一个核心原则,“只负责你拥有的对象的生命周期”,也就是说,如果你对一个对象有所有权,那么你就要负责其回收的工作,否则,你不需要,也不能取回收你不拥有的对象。那些对象属于“拥有”范畴呢?
1:所有使用alloc, new, copy或mutabelCopy,以及这些关键词开头的函数返回的对象,你都是拥有所有权的,也就是要负责这些对象的内存回收工作。这是iOS开发中的一种约定,所以,当你编写自己的alloc,
new或copy类型的函数时,也请遵循这样的命名规范。
2:retain返回的对象,拥有所有权。例如显示调用retain函数返回的结果,或者synthesize 的retain类型的成员变量。
3:所有使用其他函数返回的对象,没有所有权。
4:返回的对象的引用,没有所有权。
5:autorelease返回的对象没有所有权。

6.1 IOS平台引用计数机制

Cocoa中提供了一个机制来实现上面提到的这个逻辑模型,它被称为”引用计数”。
每个对象都有一个引用计数(retainCount),对象被创建的时候引用计数为1;当引用计数值为0的时候,对象将被系统销毁。



引用计数是实例对象的内存回收唯一参考:引用计数(retainCount)是Objective-C管理对象引用的唯一依据。调用实例的release方法后,此属性减1,减到为0时对象的dealloc方法被自动调用,进行内存回收操作,也就是说我们永不该手动调用对象的dealloc方法。

一,它的内存管理API主要操作接口:
alloc, allocWithZone,new(带初始化):为对象分配内存,retainCount为“1”,并返回此实例;
retain:retainCount 加“1”
copy,mutableCopy:复制一个实例,retainCount数为“1”,返回此实例。所得到的对象是与其它上下文无关的,独立的对象(干净对象)。
release:retainCount 减“1”,减到“0”时调用此对象的dealloc方法
autorelease:在当前上下文的AutoreleasePool栈顶的autoreleasePool实例添加此对象,由于它的引入使Objective-C(非GC管理环境)由全手动内存管理上升到半自动化。

二.dealloc方法介绍
dealloc方法会在对象引用计数为0的时候被系统调用。
dealloc不要手动调用,而是让系统自动调用(对象引用计数为0时);
对象释放的时候需要把自己所包含的对象变量也一并释放掉。
-(void) dealloc{
[name release];
[super dealloc];
}
在dealloc方法中对变量的释放顺序应与初始化的顺序相反,最后调用[super dealloc];

三,内存管理
alloc:为一个新对象分配内存,并且它的引用计数为1。调用alloc方法,你便有对新对象的所有权。
copy:制造一个对象的副本,改副本的引用计数为1,调用者具有对副本的所有权。
retain:使对象的引用计数加1,并且获得对象的所有权。
release:使对象的引用计数减1,并且放弃对象的所有权。
autorelease:也能使对象的引用计数减1,只不过是在未来的某个时间使对象的引用计数减1。

6.2 IOS 平台AutoRelease池

Autorelease池的机制,为你提供了一个“延时”release对象的机制。当你既想放弃对象所有权,又不想立即发生放弃行为的时候(比如你在方法中,把一个持有的对象作为返回值return)。这时候,你不是必须要创建一个autorelease 池,但个别情况下,你就必须这么做,或者如果你这么做了,是有益的。
iOS也提供了一种延时释放的机制 AutoRelease(也称为半自动释放机制),以这种方式申请的内存,开发者无需手动释放,系统会在某一时机释放该内存;

6.2.1 关于Autorelease池

Autorelease池是NSAutorelease类的一个实例,它是得到了autorelease 消息的对象的容器。在autorelease池被dealloc 的时候,它自己会给容纳的所有对象发送release消息。一个对象可以被多次放到同一个autorelease 池,每一次放入(发送autorelease 消息)都会造成将来收到一次release。

多个autorelease 池之间的关系,虽然通常的描述是“嵌套关系”,实际上它们是按照栈(stack)的方式工作的(译者:即类似于后进先出的队列)。当一个新的autorelease池创建后,它就位于这个栈的最顶端。池被dealloc 的时候,就从栈中删除。当对象收到autorelease 消息时,实际上它会被放到“这个线程”“当时”位于栈的最顶端的那个池中(译者:由此可以推定,每个线程都有一个私有的autorelease池的栈)。

Cocoa 希望程序中长期存在一个autorelease 池。如果池不存在,autorelease 的对象就无从release了,从而造成内存泄露。当程序中没有autorelease池,你的程序还给对象发送autorelease消息,这是Cocoa会发出一个错误日志。AppKit和UIKit框架自动在每个消息循环的开始都创建一个池(比如鼠标按下事件、触摸事件)并在结尾处销毁这个池。正因为如此,你实际上不需要创建autorelease 池,甚至不需要知道创建autorelease池的代码如何写。
下面三种情形下,你却应该使用你自己的autorelease池:
1,如果你写的程序,不是基于UI Framwork。例如你写的是一个基于命令行的程序。
2,如果你程序中的一个循环,在循环体中创建了大量的临时对象。
你可以在循环体内部新建一个autorelease 池,并在一次循环结束时销毁这些临时对象。这样可以减少你的程序对内存的占用峰值。
3,如果你发起了一个secondary线程(译者:main 线程之外的线程)。这时你“必须”在线程的最初执行代码中创建autorelease 池,否则你的程序就内存泄露了。(参看“Autorelease 池和线程”)

通常我们用alloc 和init来新建一个NSAutoreleasePool对象,用drain 消息来销毁这个池。如果你发送autorelease 或者retain 消息给一个autorelease 池对象,就会出现程序异常。如果要理解release 和drain 之间的区别,请参看“垃圾内存回收”。 Autorelease池必须在其所“诞生”的上下文环境(比如对方法或函数的调用处,或者循环体的内部)中进行drain。
Autorelease 池必须以inline 的方式使用,你永远不需要把一个这样的池作为对象的成员变量来处理。

6.2.2 使用本地Autorelease 池来减少内存占用峰值

许多程序所使用的临时对象都是autorelease 的,因此这些对象在池被销毁前是占用内存的。要想减少对内存的占用峰值,就应该使用本地的autorelease 池。当池被销毁时,那些临时对象都将被release,进而系统占用内存情况得以改善。
下面的例子,是在for循环中使用本地autorelease池:
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
NSError *error = nil;
NSString *fileContents = [[[NSString alloc] initWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error] autorelease];
/* Process the string, creating and autoreleasing more objects. */
[loopPool drain];
}
这个for 循环每次处理一个文件。在循环体的开始,创建了一个池,结束时销毁了这个池。那么任何收到了autorelease 消息的对象(本例中的fileContents)都会被存放于池(loopPool)中。当池在单次循环结束时,进行销毁,这些对象也就release了。

在 autorelease池已经dealloc 之后,那些曾经收到autorelease消息对象,只能被视为失效,而不要再给他们发消息,或者把他们作为返回值进行返回。如果你必须在autorelease 之后还要使用某个临时对象,你可以先发一个retain 消息,然后等到这时的池已经调用了drain 之后,再发送autorelease 消息。示例如下:



在subPool 有效的时候,我们给match对象发送了retain消息。在subPool被drain之后,我们又给match发送了autorelease消息。这样做的结果,就是match没有进入subPool 池,而是进入了subPool 的更早一个入栈的池。这样实际上是延长了match 对象的生命周期,使得match 可以在循环体之外还能接收消息,还使得match可以作为返回值来返回给findMatchingObject的调用者。

6.2.3 Autorelease 池和线程

Cocoa程序的每一个线程都维护着一个自己的NSAutorelease对象的栈。当线程结束的时候,这些池对象就会被release。如果你写的程序仅仅是一个基于Foundation的程序,又或者你detach一个线程(译者:关于detached
thread,请参考 Threading Programming Guide),你需要新建一个你自己的autorelease池。

如果你的程序是一个要长期运行的程序,可能会产生大量的临时对象,这是你必须周期性地销毁、新建autorelease 池(Kit 在主线程中就是这么做的),否则autorelease 对象就会累积并吃掉大量内存。如果你detached 线程不调用Cocoa,你就不必新建autorelease池。

注意:除非是Cocoa 运行于多线程模式,否则如果你使用POSIX 线程API 来启动一个secondary 线程,而不是使用NSThread,你是不能使用Cocoa 的,当然也就不能使用
NSAutorelease 池。Cocoa 只有在detach 了它第一个NSThread 对象之后,才能进入多线程模式。为了在secondary
POSIX线程中使用Cocoa,你的程序首先要做的是detach至少1个NSThread,然后立刻结束这个线程。你可以用NSThread的isMultiThreaded 方法来检测Cocoa 是否处于多线

6.2.4 Autorelease池的作用域(Scope)和嵌套

Autorelease池常常被称为“嵌套”的。但实际上也可以把这些嵌套的池理解为在一个栈(stack)中:嵌套在最里面的池,位于栈的最顶端。每个线程都有一个autorelease 池的栈,而你每新建一个池,它都会被放置在这个栈的最顶端。当一个对象收到了autorelease 消息时,又或者这个对象作为addObject:方法的参数传递的时候,这个对象被放到了当时这个栈中最顶端的那个池中。

一个autorelease 池的“作用域”(Scope)实际是由它在栈中的位置所决定的。最顶上的池,就是当下存放autorelease 对象的池。如果这是新建了一个新的池,原有的池就离开了Scope,知道这个新池被drain 后,原有的池再次回到最顶端,进入scope。Drain后的池,就永远不再Scope了。

如果你drain一个池,但是这个池却不在栈顶,那么栈内位于它上面的所有池就都drain 了(这意味着所有他们容纳的对象,都收到release 消息)。如果你不小心忘记了调用一个池的drain,那么从嵌套结构上看,更外一层的池在drain的时候,会销毁这个池。
这种特性,对于出现程序运行异常的情形是有用的。当异常出现,系统立刻从当前执行的代码中跳出,当前现场有效的池被drain。然而一旦这个池不是栈顶的那个池,那么所有它上面的池都被drain。最终,比这个池更早的那个池成为栈顶。这种行为机制,是的Exception Handler不必去处理异常发生所在现场autorelease对象的release工作。对Exception Handler而言,既不希望也不必要去给autorelease池发送release---Unless
the heandler is re-raising the exception。

6.2.5 内存垃圾回收

尽管内存垃圾回收系统(Garbage Collection Programming Guide 中讲述)并不使用
autorelease 池,但如果你开发的是一个混合框架(既用到了内存垃圾回收,还用到了引用计数),那么autorelease 池为垃圾内存回收者提供了线索。当autorelease池进行release 的时候,恰恰是提示垃圾内存回收者现在需要回收内存。
在垃圾内存回收的环境中,release 实际上什么都不做。正因为如此,
NSAutoreleasePool 才提供了一个drain方法—这个方法在引用计数环境下等同release,但是在垃圾内存回收环境下,触发了内存回收行为(前提是此时内存新分配的数量,超过了阈值)。所以,如果要销毁autorelease池,你应该用drain,而不是release。

7 IOS内存管理ARC技术

自动引用计数(Automatic Reference Counting),或ARC,的方式。系统采用和MRR 相同的引用计数系统,但是在编译时(Compile-time)插入了内存管理的方法。对于新的工程项目,强烈建议使用ARC 方式,这样你不需要理解本文所述的底层实现。不过,个别情况下,你会受益于对这些底层实现的理解。ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存;更多关于ARC的讲述,请参看Transitioning
To ARC Release Notes。ARC是一个编译期的技术,利用此技术可以简化Objective-C编程在内存管理方面的工作量。

ARC技术是随着XCode4.2一起发布的,在缺省工程模板中,你可以指定你的工程是否支持ARC技术,如果你不指定工程支持ARC技术,在代码中你必须使用管理内存的代码来管理内存。

本章节内容详见IOS ARC技术官网,这里不做过多介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: