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

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

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

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

先来看最初的版本:

Engine.h




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




Engine.m




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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自己的引擎。

先来考虑第一种情况:

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




1
2
3
4
5
6
7
8
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方法有了第二个版本:




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




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

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




1
2
3
4
5
6
7
8
9
10
11
12
13
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的第三个版本出现了:




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




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

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




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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资源最终都得到了释放!

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于YOLOv9实现工业布匹缺陷(破洞、污渍)检测系统python源码+详细运行教程+训练好的模型+评估 【使用教程】 一、环境配置 1、建议下载anaconda和pycharm 在anaconda中配置好环境,然后直接导入到pycharm中,在pycharm中运行项目 anaconda和pycharm安装及环境配置参考网上博客,有很多博主介绍 2、在anacodna中安装requirements.txt中的软件包 命令为:pip install -r requirements.txt 或者改成清华源后再执行以上命令,这样安装要快一些 软件包都安装成功后才算成功 3、安装好软件包后,把anaconda中对应的python导入到pycharm中即可(不难,参考网上博客) 二、环境配置好后,开始训练(也可以训练自己数据集) 1、数据集准备 需要准备yolo格式的目标检测数据集,如果不清楚yolo数据集格式,或者有其他数据训练需求,请看博主yolo格式各种数据集集合链接:https://blog.csdn.net/DeepLearning_/article/details/127276492 里面涵盖了上百种yolo数据集,且在不断更新,基本都是实际项目使用。来自于网上收集、实际场景采集制作等,自己使用labelimg标注工具标注的。数据集质量绝对有保证! 本项目所使用的数据集,见csdn该资源下载页面中的介绍栏,里面有对应的下载链接,下载后可直接使用。 2、数据准备好,开始修改配置文件 参考代码中data文件夹下的banana_ripe.yaml,可以自己新建一个不同名称的yaml文件 train:训练集的图片路径 val:验证集的图片路径 names: 0: very-ripe 类别1 1: immature 类别2 2: mid-ripe 类别3 格式按照banana_ripe.yaml照葫芦画瓢就行,不需要过多参考网上的 3、修改train_dual.py中的配置参数,开始训练模型 方式一: 修改点: a.--weights参数,填入'yolov9-s.pt',博主训练的是yolov9-s,根据自己需求可自定义 b.--cfg参数,填入 models/detect/yolov9-c.yaml c.--data参数,填入data/banana_ripe.yaml,可自定义自己的yaml路径 d.--hyp参数,填入hyp.scratch-high.yaml e.--epochs参数,填入100或者200都行,根据自己的数据集可改 f.--batch-size参数,根据自己的电脑性能(显存大小)自定义修改 g.--device参数,一张显卡的话,就填0。没显卡,使用cpu训练,就填cpu h.--close-mosaic参数,填入15 以上修改好,直接pycharm中运行train_dual.py开始训练 方式二: 命令行方式,在pycharm中的终端窗口输入如下命令,可根据自己情况修改参数 官方示例:python train_dual.py --workers 8 --device 0 --batch 16 --data data/coco.yaml --img 640 --cfg models/detect/yolov9-c.yaml --weights '' --name yolov9-c --hyp hyp.scratch-high.yaml --min-items 0 --epochs 500 --close-mosaic 15 训练完会在runs/train文件下生成对应的训练文件及模型,后续测试可以拿来用。 三、测试 1、训练完,测试 修改detect_dual.py中的参数 --weights,改成上面训练得到的best.pt对应的路径 --source,需要测试的数据图片存放的位置,代码中的test_imgs --conf-thres,置信度阈值,自定义修改 --iou-thres,iou阈值,自定义修改 其他默认即可 pycharm中运行detect_dual.py 在runs/detect文件夹下存放检测结果图片或者视频 【特别说明】 *项目内容完全原创,请勿对项目进行外传,或者进行违法等商业行为! 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值