如果应用程序没有内存管理机制,那么在运行过程中会占用更多的内存而且不能释放,那么内存总有用完的时刻,而导致程序崩溃。内存泄露由此得来,即程序未能释放不再使用的内存,而导致内存不足。
每种开发语言都有各异的内存管理机制,C语言中通过malloc()和free(),new和delete来进行动态的内存管理,以防止内存泄露,提高程序的执行效率。JAVA语言中有专门的垃圾回收机制,回收系统中不在使用的内存。
同样在Objective-C中也有一套完善的内存管理机制,最早的方式是基于手动引用计数的内存管理机制,顾名思义就是在开发过程中需要程序员亲自管理内存的获取和释放;在Mac OS X 10.7和iOS 5之后引入和自动引用计数的内存管理(Automatic Reference Counting, ARC),从而内存管理变得很透明,程序员只要遵循ARC规则,系统会自动为你管理内存,所以建议大家使用ARC管理你的程序内存。
为了更好的理解和使用ARC,我们接下来从手动引用计数管理机制出发,一步一步揭开ARC的神秘面纱。
1.手动引用计数引用计数
首先我们需要清楚引用计数的概念,简单来说,每一个对象都有一个引用计数,记录了该对象当前被多少对象所拥有,例如,当对象被初始化的时候,引用计数也初始化为1,当在一个函数内需要使用该对象,该对象的引用计数加1;当函数执行完时,该对象的引用计数减1。当引用计数减为0的时候,就释放该对象占用的内存。
也就是说,引用计数记录着程序中有多少地方使用着该对象。
1.1引用计数管理操作
系统提供一系列的API来操作引用计数,从而实现手动引用计数的内存管理:
对象操作 | Objective-C方法 | 引用计数 | 备注 |
生成并持有对象 | Alloc/new/copy/mutableCopy | 初始化为1 | |
持有对象 | Retai | 加1 | |
释放对象 | Release | 减1 | |
废弃对象 | Dealloc | | |
表1-1
id object_alloc = [[NSObject alloc] init ];
/id object_new = [NSObject new];
通过上面的操作我们就建立了一个object对象,它的引用计数为1。
[object_alloc retain];
通过retain操作后,object_alloc对象的引用计数加1。
[object_alloc release];
通过release操作后,object_alloc对象的引用计数减1,现在变为1。
如果此时手动或者系统运行结束时,系统会在此调用
[object_alloc release];
此时object_alloc的引用计数变为0,会调用dealloc函数来释放对象占用的内存。
现在我写下如下代码,大家看看有没有错误:
id object = [[NSObject alloc] init ];// 对象有一个value属性,可以读取。
id object_o = object; // 对象object_o指向object对象,但是object的引用计数不变
// [object_o retain];//object的引用计数加1,表示object_o拥有对象object
[object release];// 此时调用release操作,因为object对象的引用计数为0而被回收。
[object_o value]; // 向一个不存在的对象发送消息,会发生操作。
正确的执行流程:
id object = [[NSObject alloc] init ];// 对象有一个value属性,可以读取。
id object_o = object; // 对象object_o指向object对象,但是object的引用计数不变
[object_o retain];//object的引用计数加1,表示object_o拥有对象object
[object release];// 此时调用release操作,因为object对象的引用计数为0而被回收。
[object_o value]; // 向一个不存在的对象发送消息,会发生操作。
所以需要在object_o指向object对象之后,增加object的引用计数,表示该对象被object_o对象所拥有,代码如上面注释掉的一行。
所以总结手动引用计数的管理方式:
- 自己生成的对象,自己所拥有
- 非自己生成的对象,自己也能拥有
- 不在需要自己拥有的对象时 释放
- 非自己拥有的对象无法释放
1.2自动释放池
自动释放池是内存管理中一个很重要的模块,尤其是在ARC中。首先我们来具体看看自动释放池的使用方式。
自动释放池的功能类似于C语言中的函数功能,在函数中定义的变量,当执行超出函数体时,内部变量就被自动释放了,变得不可用。同样,自动释放池中管理的对象,首先初始化一个自动释放池对象,然后调用autorelease操作将建立的对象加入到自动释放池中,然后调用drain操作销毁自动释放池。
NSAutorelease *pool = [[NSAutoreleasePool alloc]init];//
id object = [[NSObject alloc] init];
[object autorelease];//只是将对象添加到释放池中,不会增加该对象的引用计数。
[pool drain];//此时会向自动释放池中的每一个对象发送release操作,进行销毁。
自动释放池的好处就是程序员可以自定义自动释放池中对象的释放时机,在一定程度上简化了内存管理方式。
2.ARC(Automatic Reference Counting)
如果在编程开始选择使用ARC作为内存管理方式,那么在程序中不能出现在手动引用计数中提到的以下操作:
- Retain/release/retainCount/autorelease
- 不能显示的调用dealloc
- 使用@autorelease代替NSAutoreleasePool
实际上,程序在静态编译的过程中,系统会在你程序的适当位置自动添加上保持和释放(retain/release)的相关代码;
-(void)setValue:(id *)val{
[val retain];//可省略
[_value release];//可省略
_value = val;
}
系统添加的位置并不一定与上述方式一致,编译器会自己判断,并进行优化,删除多余的retain和release对。
那么程序员在ARC内存管理方式下,如何管理程序内存呢?
2.1属性修饰符
在ARC环境中,程序员可以通过给定义的属性变量加上不同的属性修饰符来管理对应的内存,那么有哪些属性修饰符呢?
属性声明的修饰符 | 所有权修饰符 | 引用计数的变化 |
assign | __unsafe_unreatined | 无 |
copy | __strong修饰符(被拷贝的对象) | 加1 |
retain | __strong修饰符 | 加1 |
strong | __strong修饰符 | 加1 |
unsafe_unretained | __unsafe_unreatined | 无 |
weak | __weak修饰符 | 无 |
autorelease | __autorelease | 无 |
表2-1
对象属性在定义的时候默认是strong,所以是强引用,容易出现循环引用。
2.2 循环引用
我们通过以下代码说明:
// People 类声明 在ARC环境下
@interface People :NSObject{
NSString *name;
id friend;//默认修饰符是strong,强引用
}
-(void)setFriend:(id)object;
@end
// People 类实现
#import”People.h”
@implementation People
-(void)setFriend:(id)object{
self.friend = object;
}
@end;
假如我们在使用的过程中出现如下代码:
People people_a = [[People alloc] init ];
People people_b = [[People alloc] init ];
[people_a setFriend:people_b];
[people_b setFriend:people_a];
也就是说两个对象相互引用了对方,并且friend属性在定义的时候是强引用,那么建立的两个对象会一直存在,无法释放,从而造成内存泄露,这就是所谓的循环引用。为了避免循环引用的出现,我们需要一种类型的变量,能够引用对象,但不会成为对象的所有者(即对象本身的引用计数不会加1),不影响对象本身的回收。
所以ARC中引入了弱引用的概念,通过存储一个指向对象的指针创建的,且不保留对象。通过__weak修饰符来定义:
__weak id friend; // friend 不会保留对象
[people_a setFriend:people_b];
[people_b setFriend:people_a];
在此出现类似的相互定义,就不会出现循环引用,且弱引用变量在其指向的对象释放后会自动变成nil。
3.注意事项
- 在自定义方法的名称中不要以alloc/new/copy/mutableCopy/init等前缀命名。
- 不要在ARC环境中出现retain/release/retainCount/autorelease等操作符。
以上是自己学习的总结,如果错误的地方,还请大家指正,后面会给大家继续增加各个模块的实现原理介绍。