• ObjC(ObjectiveC)的内存管理之实例分析


    作者:菩提树下的杨过
    出处:http://www.cnblogs.com/yjmyzz/archive/2011/02/24/1964245.html
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

    注:这是《Objective-C基础教程》一书上的实例,但是原书限于篇幅,分析得比较简单,初次阅读看得比较费劲,这里展开详细讨论一下。

    场景:有二个类Car和Engine,即“汽车”和“引擎”。

    先来看最初的版本:

    Engine.h

    1 #import <Cocoa/Cocoa.h>
    2
    3 @interface Engine : NSObject
    4
    5 @property int flag;
    6
    7 @end // Engine

    Engine.m

    1#import "Engine.h"
    2
    3@implementation Engine
    4
    5@synthesize flag;
    6
    7- (NSString *) description
    8{
    9 return ([NSString stringWithFormat:@"I am engine %d,my retainCount=%d",flag,[self retainCount]]);
    10} // description
    11
    12
    13-(void) dealloc
    14{
    15 NSLog(@"this engine %d is going to die.",flag);
    16 [super dealloc];
    17 NSLog(@"this engine %d is dead.",flag);
    18}
    19@end // Engine

    代码不复杂,略加解释:Engine类有一个flag属性,用于后面辅助输出时区分当前引擎的唯一标识。然后就是description方法(相当 于c#中Object的toString()方法),用于返回一个描述自身的字符串。最后就是dealloc方法,用于清理自身所用的资源。

    Car.h

    1 #import <Cocoa/Cocoa.h>
    2
    3 #import "Engine.h"
    4
    5 @interface Car : NSObject
    6 {
    7 Engine *engine;
    8 }
    9
    10 @property int flag;
    11
    12  - (void) setEngine: (Engine *) newEngine;
    13
    14  - (Engine *) engine;
    15
    16 @end // Car

    Car.m

    1#import "Car.h"
    2#import "Engine.h"
    3
    4
    5@implementation Car
    6
    7@synthesize flag;
    8
    9- (id) init
    10{
    11 if (self = [super init]) {
    12 engine = [Engine new]; //每辆汽车诞生时,先预设了一个空的引擎(flag=0的engine),这个对象最终也需要释放!
    13 }
    14 return (self);
    15} // init
    16
    17
    18- (Engine *) engine
    19{
    20 return (engine);
    21} // engine
    22
    23
    24- (void) setEngine: (Engine *) newEngine
    25{
    26 engine = newEngine;
    27} // setEngine
    28
    29
    30-(void) dealloc
    31{
    32 NSLog(@"the car %d is going to die.",flag);
    33 NSLog(@"%@",engine);
    34 [engine release];//释放附属资源:引擎
    35 [super dealloc];
    36 NSLog(@"the car %d is dead.",flag);
    37}
    38
    39@end // Car

    解释一下:init方法中,给每辆汽车在出厂时预置了一个默认的引擎(其flag值为默认值0),然后setEngine方法用于给汽车设置新引擎,最后dealloc中,汽车销毁时会附带release自己的引擎。

    先来考虑第一种情况

    有一辆汽车,给它安装了新引擎,使用完后汽车销毁,但是引擎还能拿出来做其它用途(比如给其它汽车使用之类),最后新引擎也用完了,销毁!

    1Car *car1 = [Car new];
    2car1.flag = 1;
    3Engine *engine1 = [Engine new];
    4engine1.flag = 1;
    5[car1 setEngine:engine1];
    6[car1 release];
    7NSLog(@"%@",engine1);//这里模拟引擎做其它用途
    8[engine1 release];

    以上代码至少有二个问题:

    1.1 Car在构造函数init里,预置的默认引擎(即flag=0的引擎)最后未被释放

    1.2 Car在dealloc方法中,已经释放了engine,所以Car释放后,该引擎也就跟着灰飞烟灭了,没办法再做其它用途。所以第7,8行代码根本没办法运行,会直接报错!这比内存泄漏更严重。

    先来解决最严重的第2个问题,至少让它跑起来再说,根源在于:Car销毁时,附带把engine也给release了!解决它的途径有二种:

    1、去掉Car.m类dealloc中的[engine release],但是本着“自家的孩子自己管”的原则,不推荐这种不负责任的做法。

    2、在setEngine方法中,人工调用[newEngine retain]方法,让引擎的引用计数加1,这样正好可抵消Car.m类dealloc方法中[engine release]带来的影响(一加一减,正好抵消!)。

    于是Car.m中的setEngine方法有了第二个版本:

    1- (void) setEngine: (Engine *) newEngine
    2{
    3 engine = [newEngine retain];
    4} // setEngine

    再次编译,总算通过了,也能运行了。先把问题1.1丢到一边,再来考虑第二种情况

    又有一辆汽车,安装了新引擎engine1,然后试了一下,觉得不爽,于是把engine1丢了,然后又换了另一个引擎engine2(喜新厌旧!)

    1Car *car1 = [Car new];
    2car1.flag = 1;
    3Engine *engine1 = [Engine new];
    4engine1.flag = 1;
    5[car1 setEngine:engine1];//换新引擎engine1
    6[engine1 release];//觉得不爽,于是把engine1扔了
    7
    8Engine *engine2 = [Engine new];
    9engine2.flag = 2;
    10[car1 setEngine:engine2];//又换了新引擎engine2
    11
    12[car1 release];//使用完以后,car1报废
    13[engine2 release];//新引擎engine2当然也不再需要了

    同样有二个问题:

    2.1 engine1先被new了一次,然后在setEngine中又被retain了一次,也就是说其retainCount为2,虽然代码中后来release了一次,但是也只能让retainCount减到1,并不能销毁!

    2.2 刚才1.1中所说的问题依然存在,即Car在init方法中预置的默认引擎engine0,始终被无视了,未得到解脱。

    可能,你我都想到了,在setEngine方法中,可以先把原来的旧引擎给干掉,然后再把新引擎挂上去,这样就ok了! 好吧,setEngine的第三个版本出现了:

    1- (void) setEngine: (Engine *) newEngine
    2{
    3 [engine release];
    4 engine = [newEngine retain];
    5} // setEngine

    貌似皆大欢喜了,但是事情还没完,又有新情况了:第三种情况

    有二辆汽车Car1与Car2,Car1换了新引擎engine1,然后跑去跟Car2显摆,Car2觉得新引擎不错,于是要求跟Car1共用新引 擎engine1,但问题是:在Car2尚未下手前,engine1已经被某人(可能是car1自己,也可能是车主main()函数)给抛弃了!

    1Engine *engine1 = [Engine new];//engine1.retainCount=1
    2engine1.flag = 1;
    3
    4Car *car1 = [Car new];
    5car1.flag = 1;
    6
    7Car *car2 = [Car new];
    8car2.flag = 2;
    9
    10[car1 setEngine:engine1];//car1换了新引擎engine1
    11[engine1 release];//然后很快又抛弃了它
    12
    13[car2 setEngine:[car1 engine]];//car2要跟car1共用engine1
    14
    15//最后car1跟car2都被车主main函数给扔了
    16[car2 release];
    17[car1 release];

    问题:在16行[car2 release]时,car2已经彻底把engine1给销毁了(也许car2忘记了,engine1是它跟car1共同的财产),于是紧接着[car1 release]时,car1的dealloc方法在[engine release]时,意外发现engine1已经不在人世了,最终它愤怒了,整个程序也就罢工了!

    setEngine的最后一个版本

    1- (void) setEngine: (Engine *) newEngine
    2{
    3 [newEngine retain];
    4 [engine release];
    5 engine = newEngine;
    6
    7} // setEngine

    其实就是把上一个版本的二行代码,拆分成了三行,变成了先retain,再release,看上去好象含义一样,但是仔细分析你会发现,如果当engine与newEngine为同一个对象的引用时(即这二指针指向的为同一块内存),且newEngine(其实也就是engine)的retainCount为1时,原来的版本会导致newEngine(其实也就是engine)销毁,而现在这样处理后,即会被保留下来。

    最后验证一个最终版本是否能完美应付上面提到的三种情况:

    第一种情况的运行结果:

    2011-02-25 09:17:52.951 CarParts[257:a0f] this engine 0 is going to die.
    2011-02-25 09:17:52.957 CarParts[257:a0f] this engine 0 is dead.
    2011-02-25 09:17:52.959 CarParts[257:a0f] the car 1 is going to die.
    2011-02-25 09:17:52.961 CarParts[257:a0f] I am engine 1,my retainCount=2
    2011-02-25 09:17:52.962 CarParts[257:a0f] the car 1 is dead.
    2011-02-25 09:17:52.966 CarParts[257:a0f] I am engine 1,my retainCount=1
    2011-02-25 09:17:52.968 CarParts[257:a0f] this engine 1 is going to die.
    2011-02-25 09:17:52.969 CarParts[257:a0f] this engine 1 is dead.

    第二种情况的运行结果:

    2011-02-25 09:19:30.639 CarParts[291:a0f] this engine 0 is going to die.
    2011-02-25 09:19:30.644 CarParts[291:a0f] this engine 0 is dead.
    2011-02-25 09:19:30.646 CarParts[291:a0f] this engine 1 is going to die.
    2011-02-25 09:19:30.648 CarParts[291:a0f] this engine 1 is dead.
    2011-02-25 09:19:30.650 CarParts[291:a0f] the car 1 is going to die.
    2011-02-25 09:19:30.652 CarParts[291:a0f] I am engine 2,my retainCount=2
    2011-02-25 09:19:30.653 CarParts[291:a0f] the car 1 is dead.
    2011-02-25 09:19:30.655 CarParts[291:a0f] this engine 2 is going to die.
    2011-02-25 09:19:30.657 CarParts[291:a0f] this engine 2 is dead.

    第三种情况的运行结果:

    2011-02-25 09:21:02.549 CarParts[324:a0f] this engine 0 is going to die.
    2011-02-25 09:21:02.554 CarParts[324:a0f] this engine 0 is dead.
    2011-02-25 09:21:02.556 CarParts[324:a0f] this engine 0 is going to die.
    2011-02-25 09:21:02.558 CarParts[324:a0f] this engine 0 is dead.
    2011-02-25 09:21:02.559 CarParts[324:a0f] the car 2 is going to die.
    2011-02-25 09:21:02.561 CarParts[324:a0f] I am engine 1,my retainCount=2
    2011-02-25 09:21:02.563 CarParts[324:a0f] the car 2 is dead.
    2011-02-25 09:21:02.571 CarParts[324:a0f] the car 1 is going to die.
    2011-02-25 09:21:02.573 CarParts[324:a0f] I am engine 1,my retainCount=1
    2011-02-25 09:21:02.575 CarParts[324:a0f] this engine 1 is going to die.
    2011-02-25 09:21:02.578 CarParts[324:a0f] this engine 1 is dead.
    2011-02-25 09:21:02.587 CarParts[324:a0f] the car 1 is dead.

    从输出结果上看,不管是哪一种情况,Car以及Engine资源最终都得到了释放!

  • 相关阅读:
    Linux
    前端
    第一章 初识 MyBatis
    mysql 复习
    五 、 Kafka producer 拦截器(interceptor) 和 六 、Kafka Streaming案例
    spark graphx图计算
    四、Kafka API 实战
    三、Kafka工作流程分析
    二、Kafka集群部署
    一、KafKa概述
  • 原文地址:https://www.cnblogs.com/xingchen/p/2102709.html
Copyright © 2020-2023  润新知