Objective-C 内存管理

内存管理

内存的管理机制:引用计数

每个对象都有一个与之相关联的整数,称之为这个对象的引用计数器.当某段代码需要访问一个对象时,这段代码需要将对象的引用计数器加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的使用次数相等。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值