从苹果的官方文档来看,OC对应用程序的内存管理提供了2种方法。ios
第一种即“manual retain-release”(MRR),手动保留释放,也可理解为手动引用计数。程序员
第二种,“Automatic Reference Counting”(ARC),自动引用计数。可是ARC并不等同垃圾回收。在苹果的官方文档有这样一句话,“You are strongly encouraged to use ARC for new projects.”意思是苹果强烈建议在项目中运用ARC机制来管理内存。算法
内存管理不当的话会出现如下2种问题:安全
1.过早释放:在某处程序用完某块内存以前,就将该内存还给了“堆”。(这里的堆指的是,ios启动应用时,会为应用保留一部分空闲的RAM,这部分空闲的RAM称为堆。应用程序可随意使用堆,不会影响ios的其余部分,也不会影响其余应用。)oop
2.内存泄露:不释放已经不使用的内存会致使内存泄露,即便他历来没有被再次使用过。内存泄露会致使你的应用程序的内存使用量日益增长,这反过来有可能会致使系统性能较差或申请内存被终止。性能
要说明的一点是不论是MRR中的“经过属性机制简化存取方法”(在“存”方法中涉及到了基本的内存管理),仍是ARC。本质上都是苹果帮助程序员在开发时减小了代码量,把原来由程序员要完成的工做交给编译器去完成,从而减小软件开发的繁琐程度。优化
接下来就MRR和ARC进行详细的说明。ui
在OC中全部的类均继承于基类NSObject,那么全部的类就都有一个类方法alloc和一个实例方法dealloc。当经过向类发送alloc方法来建立类实例时,系统会从堆中分配出相应字节数的内存(注:指针类型的实例变量大小是4个字节,这是保存堆中的对象地址所需的内存空间)。例如:UIView *view = [[UIView alloc] init]; alloc会返回一个指针对象,指向新分配的内存。分配内存后,在类完成其“功能”后,还要将内存还给堆。可是不能够直接向对象发送dealloc方法,即这样写[view dealloc]是不对的,只能由对象本身向本身发送dealloc方法。对于dealloc,苹果官方文档是这么解释的:The NSObject class also defines a method, dealloc, that is invoked automatically when an object is deallocated(NSObject定义了一个方法dealloc,当对象被释放时自动调用)。那么对象什么时候释放?释放时是否安全呢?这个在OC中是经过引用计数来解决这个问题的。spa
对象建立后,这个对象就有一个全部者。对象在其生命周期能够有不一样的全部者,也能够同时有多个全部者,引用计数既是用来记录全部者的数量。当对象没有全部者时,即引用计数为0时,就会释放本身。做为对象自己不须要知道全部者是谁,只需知道全部者的个数。对象经过retain计数跟踪全部者的数量。这个是经过NSObject定义的协议与标准方法命名约定相结合的方法来实现的。引用计数实际上是在进行“责任落实”:谁建立了对象(或保留了已经建立的对象),谁就是该对象的全部者。释放对象即放弃该对象的全部权。谁有对象的全部权,谁就要负责放弃该全部权。在不能再向相应对象发送消息时,即再也不拥有指向该对象的指针时,须要放弃该全部权。但此算法没法回收循环引用的存储对象。Cocoa目前采用的就是此种机制。(Cocoa是苹果公司为Mac OS X所建立的原生面向对象的API,是Mac OS X上五大API之一,其它四个是Carbon、POSIX、X11和Java)指针
MRR能够理解为当对象建立后,会有一个全部者,即新建对象的retain计数是1.当对象获得某个全部者时,retain计数+1,当对象失去某个全部者时,调用release方法,retain计数-1.当对象没有任何全部者时,retain计数为0.对象会自动调用dealloc,将所占用的内存还给堆。用代码来实现就是:
- (id)retain
{
retainCount++;
return self;
}
- (void)release
{
retainCount--;
if(retainCount == 0){
[self dealloc];
}
}
复制代码
何为全部者?何为拥有该对象的全部权?就是当你在OC中用“alloc”, “new”, “copy”, or “mutableCopy”方法建立对象后,便是此对象的拥有者。还有就是当你在保留某一对象的值的时候,也是拥有了该对象(属性中的set方法深入的说明了这一点)。以nane属性为例,它的set方法应该写为:
- (void)setName:(NSString *)str
{
[str retain];
[name release];
name = str;
}
复制代码
这里必须先保留新对象,再释放当前对象。这是由于name和str有可能指向同一个对象。若是颠倒顺序,就有可能释放掉本来打算做为name保留的对象。在类中,当类拥有其它实例对象的时候,要在dealloc方法中将其release掉。
1.若是用来建立对象的方法,其方法名是以alloc或new开头的,或是包含copy和mutableCopy,那么你已经拥有该对象的全部权。你要负责在不须要该对象的时候将其释放。
2.若是你不拥有某个对象,可是要确保该对象继续存在,那么能够经过向其发送retain消息来得到全部权(retain计数+1)。
3.当你拥有某个对象而且再也不须要该对象的时候,要release或autorelea掉。(下面会详细介绍autorelease)
4.只要对象还有至少一个全部者,该对象就会继续存在下去,只有在retain计数为0时,才会收到dealloc消息。
苹果官网是这样解释的:自动释放池块提供了一种机制,让你能够放弃对象的全部权,但要避免它被当即释放(例如,当您返回一个对象的类方法)的可能性。一般状况下,你并不须要建立本身的autorelease池块。在OC中的类方法,是为“他人”建立对象,“本身”不拥有,也不使用。那类方法中对象的内存怎么管理呢?这里须要某种解决方案,可以暂时不释放对象,但具有释放该对象的权利。经过向对象发送autorelease消息,能够将对象标记为“稍后释放”。当对象收到autorelease后,不会立刻释放,而是会加入一个NSAutoreleasePool实例。该NSAutoreleasePool实例会记录全部标记为“稍后释放”的对象。每隔一段时间,这个NSAutoreleasePool实例会被“排干(drain)”,这时它会向其包含的全部对象发送release消息,而后移除这些对象。
标记为autorelease的对象有2种命运:要么走完对象的生命周期,直到被释放,要么被另一个对象保留。当某一对象保留了标记为autorelease的对象后,那么他的retain count计数会变成2,未来的某个时候,NSAutoreleasePool实例会释放该对象,使其retain count计数为1.何为“未来的某个时候”?ios应用在运行时,存在一个运行循环(run loop)。该运行循环等待事件(event)的发生,例如触摸事件或定时器触发(NSTimer)等等,当事件发生时,应用会跳出运行循环并经过调用某个类方法来处理相应的事件。代码执行完毕后,应用将返回当前的运行循环。每次循环结束,全部标记为autorelease的对象都会收到release消息。
ARC机制极大的减小了开发过程当中常见的程序错误:retain跟release不匹配。ARC并不会消除对retain和release的调用,而是把这项本来大都属于开发者的工做移交给了编译器。ARC并不等同于垃圾回收。retain和release仍然会被调用,因此有一些开销,在release的时候可能还会调用dealloc方法。这段代码与程序员手动调用retain和release的代码在运行结果上是彻底一致的。垃圾回收机制是在运行时起做用的,会影响运行效率,而ARC是在编译时插入内存管理代码,不影响运行时效率,所以内存回收比垃圾回收时的效率要高,可以提高系统性能。这种编译器能够自由地以多种方式优化内存管理,而让程序员手动去作这些工做是不现实的。在多数状况下,使用ARC生成内存管理代码的程序比程序员手工添加内存管理代码的对等程序运行更快!
ARC不是垃圾回收,尤为是它不能像Snow Leopard中的垃圾回收机制那样处理循环引用。所以,在ios开发中,必需要作好对强引用(strong reference)的跟踪管理以避免出现循环引用。属性关系有两种主要类型:strong和weak。至关于非ARC环境里的retain和assign。只要存在一个强引用,对象就会一直存在,不会被销毁。OC中一直存在循环引用的问题,但在实际应用中不多出现循环引用。对于过去那些使用assign属性的地方,在ARC环境中要使用weak代替。大部分引用循环是由委托(delegate)引发的,因此应该老是把delegate属性声明为weak。当引用的对象被销毁以后,weak引用会被自动设置为nil,与assign相比这是一个巨大的进步,由于assign能够指向被释放掉的内存,致使程序奔溃。
近现代的垃圾回收实现方法,经过按期对若干根储存对象开始遍历,对整个程序所拥有的储存空间查找与之相关的存储对象和没相关的存储对象进行标记,而后将没相关的存储对象所占物理空间回收。既经过一系列的GC Roots的对象做为起始点,从这些根节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的。便是可回收的。此算法可回收循环引用的存储对象。(Java和C#语言采用的机制)
引伸阅读:深拷贝和浅拷贝
深拷贝:简单说就是对指针指向的内容进行拷贝,以字符串为例,就是指建立一个新的指针在一个新的地址区域建立一个字符串,这个字符串与原字符串值相同,新的指针指向这个新建立的字符串。而原字符串的引用计数没有+1
浅拷贝:既指针拷贝,例如一个指针指向一个字符串,也就是说这个指针变量的值是这个字符串的地址,那么对这个指针拷贝就是又建立了一个指针变量,这个指针变量的值是这个字符串的地址,也就是这个字符串的引用计数+1
关于深浅拷贝看源码一目了然,以NSString和NSMutableString为例:
- (id)copyWithZone:(NSZone*)zone
{
if (NSStringClass == Nil) NSStringClass = [NSString class];
return RETAIN(self)
}
- (id)mutableCopyWithZone:(NSZone*)zone
{
return [[NSMutableString allocWithZone:zone] initWithString:self];
}
复制代码
看上面代码,当属性设置copy时,实际调用的就是copyWithZone方法,而copyWithZone并无建立新的对象,而是使指针持有了原来的对象,即浅拷贝。而属性设置mutableCopy时,调用的就是mutableCopyWithZone方法,而这个方法建立了一个新的可变字符串对象,即深拷贝。