objective-C 的内存管理之-实例分析

简介: 注:这是《Objective-C基础教程》一书上的实例,但是原书限于篇幅,分析得比较简单,初次阅读看得比较费劲,这里展开详细讨论一下。 场景:有二个类Car和Engine,即“汽车”和“引擎”。 先来看最初的版本: Engine.

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

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

先来看最初的版本:

Engine.h

#import <Cocoa/Cocoa.h>

@interface Engine : NSObject

@property int flag;

@end // Engine

Engine.m

#import "Engine.h"

@implementation Engine

@synthesize flag;

- (NSString *) description
{
    return ([NSString stringWithFormat:@"I am engine %d,my retainCount=%d",flag,[self retainCount]]);
} // description


-(void) dealloc
{
	NSLog(@"this engine %d is going to die.",flag);
	[super dealloc];
	NSLog(@"this engine %d is dead.",flag);
}
@end // Engine

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

Car.h

#import <Cocoa/Cocoa.h>

#import	"Engine.h"

@interface Car : NSObject
{
    Engine *engine;	
}

@property int flag;

- (void) setEngine: (Engine *) newEngine;

- (Engine *) engine;

@end // Car

Car.m

#import "Car.h"
#import "Engine.h"


@implementation Car

@synthesize flag;

- (id) init
{
    if (self = [super init]) {
        engine = [Engine new]; //每辆汽车诞生时,先预设了一个空的引擎(flag=0的engine),这个对象最终也需要释放!       
    }
    return (self);
} // init


- (Engine *) engine
{
    return (engine);
} // engine


- (void) setEngine: (Engine *) newEngine
{
	engine = newEngine;    
} // setEngine


-(void) dealloc
{	
	NSLog(@"the car %d is going to die.",flag);
	NSLog(@"%@",engine);
	[engine release];//释放附属资源:引擎	
	[super dealloc];
	NSLog(@"the car %d is dead.",flag);
}

@end // Car

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

先来考虑第一种情况

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

Car *car1 = [Car new];
car1.flag  = 1;
Engine *engine1 = [Engine new];
engine1.flag = 1;
[car1 setEngine:engine1];
[car1 release];	
NSLog(@"%@",engine1);//这里模拟引擎做其它用途	
[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方法有了第二个版本:

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

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

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

Car *car1 = [Car new];
car1.flag = 1;
Engine *engine1 = [Engine new];
engine1.flag = 1;
[car1 setEngine:engine1];//换新引擎engine1		
[engine1 release];//觉得不爽,于是把engine1扔了

Engine *engine2 = [Engine new];
engine2.flag = 2;
[car1 setEngine:engine2];//又换了新引擎engine2

[car1 release];//使用完以后,car1报废
[engine2 release];//新引擎engine2当然也不再需要了

同样有二个问题:

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

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

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

- (void) setEngine: (Engine *) newEngine
{
	[engine release];
	engine = [newEngine retain];    
} // setEngine

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

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

Engine *engine1 = [Engine new];//engine1.retainCount=1
engine1.flag = 1;
	
Car *car1 = [Car new];
car1.flag = 1;
	
Car *car2 = [Car new];
car2.flag = 2;
	
[car1 setEngine:engine1];//car1换了新引擎engine1	
[engine1 release];//然后很快又抛弃了它
	
[car2 setEngine:[car1 engine]];//car2要跟car1共用engine1
	
//最后car1跟car2都被车主main函数给扔了
[car2 release];
[car1 release];

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

setEngine的最后一个版本

- (void) setEngine: (Engine *) newEngine
{	
	[newEngine retain];
	[engine release];	
	engine = newEngine;
    
} // 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资源最终都得到了释放!

 

2014-02-21 注:现在最新的xcode上,setEngine方法不管是第二个版本,还是第三个版本,对于第三种情况,都能正确释放所有资源。估计是xcode做了改进,这二个版本编译出来的代码,个人估计是相同的,原书上的分析应该是基于当时的xcode环境,友情提醒大家学习时注意。

目录
相关文章
|
存储 弹性计算 缓存
阿里云服务器ECS经济型、通用算力、计算型、通用和内存型选购指南及使用场景分析
本文详细解析阿里云ECS服务器的经济型、通用算力型、计算型、通用型和内存型实例的区别及适用场景,涵盖性能特点、配置比例与实际应用,助你根据业务需求精准选型,提升资源利用率并降低成本。
824 3
|
存储 分布式计算 监控
阿里云服务器实例经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i详解与选择策略
在阿里云现在的活动中,可选的云服务器实例规格主要有经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例,虽然阿里云在活动中提供了多种不同规格的云服务器实例,以满足不同用户和应用场景的需求。但是有的用户并不清楚他们的性能如何,应该如何选择。本文将详细介绍阿里云服务器中的经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例的性能、适用场景及选择参考,帮助用户根据自身需求做出更加精准的选择。
|
10月前
|
设计模式 缓存 Java
【JUC】(4)从JMM内存模型的角度来分析CAS并发性问题
本篇文章将从JMM内存模型的角度来分析CAS并发性问题; 内容包含:介绍JMM、CAS、balking犹豫模式、二次检查锁、指令重排问题
251 1
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
959 4
AI代理内存消耗过大?9种优化策略对比分析
|
12月前
|
存储 机器学习/深度学习 缓存
阿里云九代云服务器怎么样?计算型c9i、通用型g9i、内存型r9i实例介绍
阿里云第9代云服务器主要实例规格包括计算型c9i、通用型g9i、内存型r9i,本文将为大家介绍阿里云九代云服务器中的计算型c9i、通用型g9i、内存型r9i实例的主要性能特点,并分享最新的活动价格信息,以供参考。
984 1
|
存储 缓存 分布式计算
高内存场景必读!阿里云r7/r9i/r8y/r8i实例架构、性能、价格多维度对比
阿里云针对高性能需求场景,一般会在活动中推出内存型r7、内存型r9i、内存型r8y和内存型r8i这几款内存型实例规格的云服务器。相比于活动内的经济型e和通用算力型u1等实例规格,这些内存型实例在性能上更为强劲,尤其适合对内存和计算能力有较高要求的应用场景。这些实例规格的云服务器在处理器与内存的配比上大多为1:8,但它们在处理器架构、存储性能、网络能力以及安全特性等方面各有千秋,因此适用场景也各不相同。本文将为大家详细介绍内存型r7、r9i、r8y、r8i实例的性能、适用场景的区别以及选择参考。
|
存储 分布式计算 安全
阿里云服务器内存型实例怎么选?r7/r8y/r8i实例性能、适用场景与选择参考
在选择阿里云服务器时,针对内存密集型应用和数据库应用,内存型实例因其高内存配比和优化的性能表现,成为了众多用户的热门选择。在目前阿里云的活动中,内存型实例主要有内存型r7、内存型r8y和内存型r8i实例可选。为了帮助大家更好地了解这三款实例的区别,本文将详细对比它们的实例规格、CPU、内存、计算、存储、网络等方面的性能,并附上活动价格对比,以便用户能够全面了解它们之间的不同,以供选择和参考。
|
存储 编解码 安全
阿里云高性能企业级甄选Intel第八代计算型c8i、通用型g8i和内存型r8i实例简介
计算型c8i、通用型g8i和内存型r8i实例是阿里云推出的高性能企业级甄选Intel第八代云服务器实例,采用CIPU+飞天技术架构,搭载最新的Intel 第五代至强可扩展处理器(代号EMR),性能进一步大幅提升,同时拥有AMX加持的AI能力增强,并在全球范围率先支持TDX机密虚拟机能力,实现了AI增强和全面安全防护的两大特色优势。本文将为您介绍这三个实例规格的性能、适用场景及最新活动价格以及选择指南,以供选择参考。
695 18
|
存储 缓存 数据挖掘
阿里云服务器实例选购指南:经济型、通用算力型、计算型、通用型、内存型性能与适用场景解析
当我们在通过阿里云的活动页面挑选云服务器时,相同配置的云服务器通常会有多种不同的实例供我们选择,并且它们之间的价格差异较为明显。这是因为不同实例规格所采用的处理器存在差异,其底层架构也各不相同,比如常见的X86计算架构和Arm计算架构。正因如此,不同实例的云服务器在性能表现以及适用场景方面都各有特点。为了帮助大家在众多实例中做出更合适的选择,本文将针对阿里云服务器的经济型、通用算力型、计算型、通用型和内存型实例,介绍它们的性能特性以及对应的使用场景,以供大家参考和选择。
|
弹性计算 固态存储 ice
阿里云服务器ECS内存型2核16G、4核32G和8核64G配置实例、费用和性能参数表
本文整理了2025年阿里云服务器租赁价格表,涵盖2核16G、4核32G和8核64G配置收费标准。CPU内存比为1:8,提供多种实例规格如ECS内存型r8i、通用算力型u1等。价格由CPU内存、公网带宽及系统盘组成,支持优惠折扣(年付6.7折起)。文中详细列出各配置参考价格、公网带宽与系统盘收费,并对比不同实例规格性能,如Intel Xeon和AMD EPYC处理器系列,帮助用户选择高性价比方案。具体价格以阿里云官网为准。
1757 4