内存管理
内存的管理机制:引用计数
每个对象都有一个与之相关联的整数,称之为这个对象的引用计数器.当某段代码需要访问一个对象时,这段代码需要将对象的引用计数器加1,表示我要拥有或者访问这个对象.当这段代码结束对象访问时,需要将对象的引用计数器减1,表示它将不再拥有或访问这个对象.当此对象的保留计数器为0时,表示不再有代码需要访问此对象.因此对象的生命周期将结束,其占用的内存被系统回收.(简而言之:当一个对象被分配内存之后,他的引用计数器是1,当需要使用时,需要保留,不用的时候,减一,当引用计数为零时,需要销毁,销毁时,需要调用 dealloc 方法,完成与之相关的对象的销毁)
- alloc、new、copy(copy生成接收对象的一个副本)//使用这三个方法创建对象时,对象的引用计数器为1
- (id) retain;// 给对象发送 return 消息后,对象的引用计数器加1
- (void)release;// 向对象发送 release 消息后,对象的引用计数器减1
- (void) dealloc;// 当一个对象的引用计数器变为0而即将被销毁时 ,Objective-C 自动想对象发送一条 dealloc 消息,我们通常都会在自己的对象中重写 dealloc 方法
- (unsigned) retainCount;// 获取当前对象的引用计数器的值
接下来演示一个例子,来帮助大家理解引用计数器的机制
首先建立一个 QYStudent 的类继承与 NSObject 类,然后只需要在 QYStudent.m 文件中实现输出一句话和引用计数器为0时的delloc 的方法,并且也输出一句话代表要销毁这个对象了.
#import "QYStudent.h"
@implementation QYStudent
- (NSString *)description
{
return @"hello-student";
}
- (void)dealloc
{
NSLog(@"我毕业了");
[super dealloc];
}
@end
注意:这里的[ super delloc] 是因为手动管理内存时,当对象所对应的引用计数器为0时,需要销毁此对象来释放内存,此时,应首先从超类中开始释放内存,如果是子类中已释放了对象,而超类中还保存有此对象的内存,会造成内存的泄露.
在 main 中实现一下 retain 后的结果
#import <Foundation/Foundation.h>
#import "QYStudent.h"
int main(int argc, const char * argv[]) {
//实例化一个QYStudent对象
QYStudent *stu = [QYStudent new];
// 查看一下他的引用计数器值
NSLog(@"%lu",[stu retainCount]);
// 对引用计数器实现加1操作
[stu retain];
// 再查看引用计数器的值
NSLog(@"%lu",[stu retainCount]);
// 再对引用计数器实现加1操作。
[stu retain];
NSLog(@"%lu",[stu retainCount]);
// 对引用计数器实现减1操作
[stu release];
// 查看对象的引用计数器
NSLog(@"%lu",[stu retainCount]);
// 再减1
[stu release];//1
NSLog(@"%lu",[stu retainCount]);
// 再减1,直到减为0
[stu release];//0
NSLog(@"%lu",[stu retainCount]);
// 当对象的引用计数器变为0的时候, Objective-C的机制会向对象发送dealloc消息查看对象的dealloc方法是否被调用了
return 0;
}
其输出的结果是
2015-06-28 10:00:11.429 MemoryMgrDemo[651:193650] 1
2015-06-28 10:00:11.430 MemoryMgrDemo[651:193650] 2
2015-06-28 10:00:11.430 MemoryMgrDemo[651:193650] 3
2015-06-28 10:00:11.430 MemoryMgrDemo[651:193650] 2
2015-06-28 10:00:11.431 MemoryMgrDemo[651:193650] 1
2015-06-28 10:00:17.864 MemoryMgrDemo[651:193650] 我毕业了
2015-06-28 10:00:32.513 MemoryMgrDemo[651:193650] 1
最后一个值为什么为1,按照我们的本意,当调用过[ super delloc] 以后, stu 的引用计数器值应该为0,释放掉了相应的内存啊.其实这里是编译器的延迟,把已释放掉的当做未释放掉才会出现这个问题
对象所有权
- 内存管理之所以复杂,感觉理解起来很难,都是因为对象的所有权问题引起的.
- 那么什么是对象所有权
如果一个对象具有指向其它对象的实例变量,则称该对象拥有这些对象
如果一个函数创建了一个对象,则称该函数拥有它创建的对象 - 下面是一段代码,根据代码分析下那个实体拥有对象
int main(int argc,const char *argv[]) {
Car *car = [[Car alloc] init];
Engine *engine = [Engine new];
[car setEngine:engine];
return 0;
}
对象engine时属于哪个实体呢?
首先由于 Car 类的对象在使用 engine, 所以不可能是 main 拥有 engine, 其次由于 main 创建了对象 engine, 后面还要用所以也不可能是 Car 类拥有 engine,那如何解决呢?
解决方法:让 Car 类保留 engine, 也就是将 engine 的引用计数器加1,变为2.这是因为 main 和 Car 两个实体都拥有 engine 对象. Car类应该在 setEngine: 方法中保留 engine 对象.而 main 函数应该释放 engine 对象,然后当 Car 对象完成其任务时再释放 engine 对象(在 dealloc), 最后 engine 对象占用的内存被回收.那么重点来了,如何设置 setEngine 的方法.
- (void)setEngine:(QYEngine *)eng{
[eng retain];
engine = eng;
}//setEngine
在其 main 函数中这样写
int main(int argc,const char *argv[]) {
QYCar *car = [QYCar new];
QYEngine *engine = [QYEngine new];//计数器为1
[car setEngine:engine];
//此时调用 setEngine 方法, engine 的计数器变成2了
[engine release];
//main 函数中使用 engine
QYEngine *anotherEngine = [car engine];
[car setEngine:anotherEngine];
[car release]
return 0;
}
通过代码分析可知,实例 engine 对象时,其计数器为1,就是指针 engine 指向内存中为它分配的一块空间,接下来向 setEngine 发送消息,计数器的值变为2,然后 release, 值又变成1,这样就出问题了,相当于 engine 对象没有释放掉,会造成内存的泄露.
下面接着修改 setEngine 方法
- (void)setEngine:(QYEngine *)eng{
[eng release];
engine = eng;
}//setEngine
这样该表面是可以避免内存最后不被泄露,可以解决上面的问题,某些 Objective-C 的教材上也是这样认为的,其实不然.我们来分析下,同样的实例化 engine 对象,计数器为1,car 向发送 setEngine发送消息,来到了 setEngine 方法中,实现的不是计数器加1,而是 release, 即减1,计数器变为了0,直接接就释放了 engine 对象的内存,好了,程序结束了. engine 对象在 main 函数中还未使用,就直接被释放掉了.
下面是比较好的解决方案
int main(int argc,const char *argv[]) {
QYCar *car = [QYCar new];
QYEngine *engine = [QYEngine new];//计数器为1
[car setEngine:engine];
//此时调用 setEngine 方法, engine 的计数器变成2了
[engine release];
//main 函数中使用 engine
QYEngine *anotherEngine = [car engine];
[car setEngine:anotherEngine];
[car release]
return 0;
}
- (void)setEngine:(QYEngine *)eng{
[eng retain];
[engine release];
engine = eng;
}//setEngine
其实涉及到内存方面的内容,最好的方法就是画示意图,这样才能明确表达内存的申请以及释放过程
内存管理总结
- “如果我使用了new , alloc 或者copy方法获得一个对象,则我必须释放或自释放该对象。”
- 如果你对对象调用了retain消息,那么你必须负责释放(release)这个对象,保证retain和release的使用次数相等。