Memory Management in Cocoa Program

本文介绍了Cocoa中的内存管理模型,包括引用计数、 autorelease机制、 autorelease pool block的使用等核心概念,并提供了具体的代码示例。
内存管理模型基于对象的引用计数。一个对象至少有一个拥有者。如果一个对象没有拥有者,运行时系统会自动destroy它。为了保证正确清除掉拥有的调用,Cocoa设定了以下的基本准则。
 
基本准则:
  • 创建一个对象,你就持有了它。(创建一个对象的方法往往是已“alloc”,“new”,“copy”或者“mutableCopy”为前序)
  • 可以通过retain去持有一个对象
  • 当不再需要对象时,就要清除掉持有对象的引用(通过发送release或者autorelease消息)
  • 不持有的对象不需要清除掉引用(例如[NSString stringWithFormat:""]的对象就是你没有持有的对象,不需要release)
 
Autorelease
 
autorelease的作用是延迟发送一个release消息。发送的时间是当前autorelease pool block的最后。
 
 
通过引用返回的对象
 
在Cocoa中,一些方法是通过引用返回的对象,这种对对象是没有持有的,不需要release。例如:NSError
 
View Code
1 NSString *fileName = <#Get a file name#>;
2 NSError *error;
3 NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
4 encoding:NSUTF8StringEncoding error:&error];
5 if (string == nil) {
6 // Deal with error...
7 }
8 // ...
9 [string release];

 

 
 
实现dealloc方法清除引用
 
当一个对象没有引用技术时,dealloc会被调用,用于清除掉该对象拥有的内存,释放资源。
 
注意,不要直接调用一个对象的dealloc方法,而且必须要调用super的dealloc方法。
 
内存管理实践
 
通过访问器方法(Accessor Methods)
 
View Code
1 @interface Counter : NSObject
2 @property (nonatomic, retain) NSNumber *count;
3 @end;

 

 
count属性的get和set方法相当于:
 
View Code
 1 - (NSNumber *)count {
 2 return _count;
 3 }
 4  
 5 - (void)setCount:(NSNumber *)newCount {
 6 [newCount retain];
 7 [_count release];
 8 // Make the new assignment.
 9 _count = newCount;
10 }

 

 
使用访问器方法设置属性值
 
如果你需要实现reset方法,重置属性值,有三种实现去使用set方法:
 
View Code
1 - (void)reset {
2 NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
3 [self setCount:zero];
4 [zero release];
5 }

 

因为zero是通过alloc创建的,所以需要release。
 
View Code
1 - (void)reset {
2 NSNumber *zero = [NSNumber numberWithInteger:0];
3 [self setCount:zero];
4 }

 

 
这里通过一个便捷的构造方法numberWithInteger去创建对象,不需要retain和release。
 
View Code
1 - (void)reset {
2 NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
3 [_count release];
4 _count = zero;
5 }

 

这种方法大多的cases中是能够正确使用的,但是很容易引致一些错误,例如,忘记retain或者release。另外注意的是,如果你使用key-value去监测它的变化,通过这种方法是不适用KVO的。
 
不要在初始化方法和dealloc方法中使用访问器方法
 
初始化方法正确的写法是:
 
View Code
1 - init {
2 self = [super init];
3 if (self) {
4 _count = [[NSNumber alloc] initWithInteger:0];
5 }
6 return self;
7 }

 

 
或者
 
View Code
1 - initWithCount:(NSNumber *)startingCount {
2 self = [super init];
3 if (self) {
4 _count = [startingCount copy];
5 }
6 return self;
7 }

 

 
dealloc的正确写法是:
 
View Code
1 - (void)dealloc {
2 [_count release];
3 [super dealloc];
4 }

 

 
 
通过弱引用防止retain循环
 
 
 
retain循环的例子:一个document对象有一个page对象,而一个page对象有一个属性去引用它所在的document对象,这就产生了retain循环。
 
解决retain循环的方法是使用弱引用,弱引用不是持有的关系。
 
上面的引用图中,为了保证完整的保持graph对象,就必须要是强引用,即retain。(因为如果不是强引用,pages和paragraphs都可能因为没有owners而导致dealloc)。Cocoa有一个约定,“父”对象对“子”对象强引用,相反为弱引用。
 
需要注意,给一个弱引用对象发消息,如果对象被dealloc了,程序就crash了。大部分情况下,被弱引用的对象应该知道引用它的对象,如果它dealloc要通知引用它的对象。例如,你注册了一个对象到notification center,notification center就持有一个对对象的弱引用。如果对象dealloc了,就需要unregister该对象,防止notification center向对象发信息。
 
Collection类持有它们的内部对象
 
当你添加一个对象到一个collection(例如array,dictionary和set)中,collection对象就持有该对象。当对象从collection移除或者collection自己release掉,引用就会被清除掉。
 
使用Autorelease Pool Block
 
autorelease pool block提供一种机制,使能够不用立刻清除掉对对象的引用。通常情况下,不需要创建自己的autorelease pool block,但是某些特殊的情况下,会有这种需求。
 
关于Autorelease Pool Block
 
View Code
1 @autoreleasepool {
2 // Code that creates autoreleasedobjects.
3 }

 

 
在autorelease pool block的最后,所有之前收到了autorelease消息的对象都会收到release消息。
 
Cocoa总是希望所有的代码都是在autorelease pool block里面执行,否则,autorelease的对象就不会收到release消息,从而就会内存泄漏。如果在autorelease pool block外,发送一个autorelease消息,Cocoa会在日志中打印一个错误得消息。Appkit和UIKit framework在一个autorelease pool block里面遍历处理所有的事件循环。所以,尽量不要创建自己的autorelease pool block,只有下面三种情况可以创建自己autorelease pool block:
  • 写一个和UI framework无关的程序,例如一个命令行工具。
  • 创建一个很多临时对象的循环。(例如,在一个循环中,产生大量的临时对象,希望在下一个循环开始之前释放这些对象)
  • 创建另外一个线程。
 
使用local autorelease pool block去降低高峰内存占用
 
好多时候要创建大量的临时对象,这些对象在autorelease pool block结束之前一直占用着内存空间,这种情况就可以创建自己的autorelease pool block。例如:
 
View Code
1 NSArray *urls = <# An array of file URLs #>;
2 for (NSURL *url in urls) {
3 @autoreleasepool {
4 NSError *error;
5 NSString *fileContents = [NSString stringWithContentsOfURL:url
6 encoding:NSUTF8StringEncodingerror:&error];
7 /* Process the string, creating and autoreleasing more objects. */
8 }
9

 

 
在autorelease pool block之后,你就应该认为对象被释放了。不要向这个对象发送消息,或者把它返回给调用者,如果你非要这样做,就需要在autorelease pool block中retain该调用,然后在autorelease pool block外面发送autorelease消息给它。例如:
 
View Code
 1 – (id)findMatchingObject:(id)anObject {
 2 id match;
 3 while (match == nil) {
 4 @autoreleasepool {
 5 /* Do a search that creates a lot of temporary objects. */
 6 match = [self expensiveSearchForObject:anObject];
 7 if (match != nil) {
 8 [match retain]; /* Keep match around. */
 9 }
10 }
11 }
12 return [match autorelease]; /* Let match go and return it. */
13 }

 

 
 
autorelease pool block和线程
 
Cocoa应用中的每一个线程都会维护有自己的autorelease pool block栈。如果你需要写一个仅仅以来Foundation的程序或者分配一个新线程,你就需要创建自己的autorelease pool block。
 
如果你的应用或者线程是长时间运行并产生大量autorelease对象的,你就需要创建autorelease pool block。否则,autoreleased 对象就会堆积,内存空间就会不断上升。如果你的分配的线程不需要调用Cocoa的东西,就不需要使用autorelease pool block。
 
 
注意:如果创建的另外一个线程使用的是POSIX 线程API而不是NSThread,你不能使用Cocoa,除非Cocoa是多线程模式的。Cocoa只有在另外创建了第一条NSThread线程后,才会进入多线程模式。所以如果要在POSIX线程中使用Cocoa,就需要应用先额外至少创建一条NSThread,这个NSThread可以立刻退出。可以用NSThread的isMutiThread方法测试一下Cocoa是否在多线程模式下。
下载前可以先看下教程 https://pan.quark.cn/s/a4b39357ea24 Job-Recommend 蚂蚁集团招聘内推(校招、社招) 校招 蚂蚁集团 2023届实习生招聘开始啦~ 蚂蚁集团是中国最大的移动支付平台【支付宝】的母公司,也是全球领先的金融科技开放平台,致力于以科技推动包括金融服务业在内的全球现代服务业的数字化升级。 我们团队归属于支付宝事业群商家开放技术部,整个大团队致力于打造蚂蚁级的开放、产品、商户等通用业务平台,实现全局业务能力与商家资源的开放共享,对内助力于商家、用户、机构等服务体系构建,对外以小程序、生活号等为抓手激活支付+X的金融生活开放生态。 我们团队是城市化策略技术部,依托于亿级的商家及用户数据,通过数据、算法、工程构建城市智能决策网络及策略体系。 我们有耐心逗比的师兄,有温柔细致的师姐,有丰富多彩的团队活动,也有各式各样的员工关怀,快来联系我们吧! ! ! 招聘对象: 11-2023.10毕业的应届毕业生 招聘流程: 简历投递->在线笔试及测评->面试->发放实习offer->实习入职 岗位类型: 【Java研发岗位】 岗位要求: 本科及以上学历,计算机、通信、数据科学与大数据技术等相关专业。 熟练掌握java技术,对多线程、数据结构等有清晰的认识。 掌握常用数据结构、算法、设计模式,熟悉MySQL/Oracle数据库等关系型数据库。 具备较强的编程能力、数据分析能力、问题排查能力,工作主动,学习能力强。 【数据工程/数据挖掘岗位】 岗位要求: 1、对分布式计算有较深的认识, 熟练使用spark,hadoop等处理海量用户行为数据。 2、熟练运用python、shell等脚本编程语言。 3、熟练掌握概率论与数理统计者优先。 4...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值