文档章节

iOS内存管理编程指南

木木情深
 木木情深
发布于 2014/02/16 21:23
字数 2145
阅读 150
收藏 4

iOS下内存管理的基本思想就是引用计数,通过对象的引用计数来对内存对象的生命周期进行控制。具体到编程时间方面,主要有两种方式:

1:MRR(manual retain-release),人工引用计数,对象的生成、销毁、引用计数的变化都是由开发人员来完成。

2:ARC(Automatic Reference Counting),自动引用计数,只负责对象的生成,其他过程开发人员不再需要关心其销毁,使用方式类似于垃圾回收,但其实质还是引用计数。

iOS不支持垃圾回收机制,这点与Mac OS有所不同。

ARC是Xcode 4.2之后加入的新特性,可能很多开发人员并不习惯使用,但使用ARC给开发带来的便利是显而易见的,鼓励大家都去尝试一下。

ARC的具体介绍及使用细则大家可以参考苹果官方文档,本文主要介绍MRR。


一:基本原则

关于MRR,我总结了一句话:是你的,就是你的;不是你的,就不是你的。

虽然看上去比较废话,但揭示了MRR内存管理里的一个核心原则,“只负责你拥有的对象的生命周期”,也就是说,如果你对一个对象有所有权,那么你就要负责其回收的工作,否则,你不需要,也不能取回收你不拥有的对象。

那些对象属于“拥有”范畴呢?

1:所有使用alloc, new, copy或mutabelCopy,以及这些关键词开头的函数返回的对象,你都是拥有所有权的,也就是要负责这些对象的内存回收工作。这是iOS开发中的一种约定,所以,当你编写自己的alloc, new或copy类型的函数时,也请遵循这样的命名规范。

2:retain返回的对象,拥有所有权。例如显示调用retain函数返回的结果,或者synthesize 的retain类型的成员变量。

3:所有使用其他函数返回的对象,没有所有权。

4:返回的对象的引用,没有所有权。

5:autorelease返回的对象没有所有权。

举例说明:

[cpp] view plaincopy

  1. //1:使用alloc函数生成,有所有权  

  2. NSString* stringA = [[NSString alloc] init];  

  3. stringA = @"abc";  

  4. [stringA release];  

  5.   

  6. //2:使用initWithString返回对象,无所有权  

  7. NSString* stringB = [NSString stringWithString: @"abc"];  

  8.   

  9. //3:显示调用retain返回对象, 拥有所有权  

  10. NSString* stringC = [[NSString stringWithString: @"abc"] retain];  

  11. stringC = @"def";  

  12. [stringC release];  

  13.   

  14. //4:retain类型成员变量,拥有所有权  

  15. @property (retain) NSString* stringD;  

  16.   

  17. @synthesize stringD = _stringD;  

  18.   

  19. - (void) dealloc  

  20. {  

  21.     [_stringD release];  

  22.       

  23.     [super dealloc];  

  24. }  

  25.   

  26. //5:通过引用返回的对象无所有权  

  27. NSString** stringERef = [self stringRef];  

  28. *stringERef = @"abc";  


二:成员变量的内存管理

注意上面的例子4,将成员变量声明为retain并synthesize时,编译器会帮你生成对应变量的setter与getter,其中, 生成的setter形式如下:

[cpp] view plaincopy

  1. - (void) setStringD: (NSString*) newString  

  2. {  

  3.     [newString retain];  

  4.     [_stringD release];  

  5.     _stringD = newString;  

  6. }  


首先获得newString的所有权,然后释放掉已经拥有的_stringD(其实此对象有可能并没有被释放,取决与其release后的引用计数是否为0), 再将newString的值赋给_stringD,这样,获得了新的,释放了旧的,就完成了对象所有权的释放与获得。注意retain与release的调用顺序,避免了同一个对象赋值引起的悬空指针问题。

当成员变量声明为assign和copy时,其生成的setter形式如下:

[cpp] view plaincopy

  1. //copy  

  2. - (void) setStringD: (NSString*) newString  

  3. {  

  4.     NSString* temp = [newString copy];  

  5.     [_stringD release];  

  6.     _stringD = temp;  

  7.       

  8. }  

  9.   

  10. //assign  

  11. - (void) setStringD: (NSString*) newString  

  12. {  

  13.     _stringD = newString;     

  14. }  


copy类型的成员变量也是拥有所有权的,也要在dealloc函数中显式释放。而assign不是。

很多人对self.stringD = @"abc"的调用形式比较困惑; 其实,编译器会将此语句自动转换为[self.setStringD:@"abc"];

还有一点要注意,声明成员类型时只有copy,没有mutableCopy,那么,如果一个成员变量是MutableArray,且被声明成copy类型,但编译器生成的setter中调用的是copy,而不是MutableCopy,所以,向MutableArray类型的成员插入数据时就会报错,因为其保存的真正类型不是MutableArray,而是Array. 这种情况有两个解决方案:一是将改变量类型声明为retain形式; 二是手动编写setter,调用mutableCopy函数。


NSObject函数声明了dealloc函数来清理内存,所有有“retain, copy”类型成员变量的类都要实现这个函数。在其内,要调用相应对象的release函数,不要忘记在【最后】调用[super dealloc];


说到成员变量的声明周期,还要提一下IBOutlet类型的变量,默认情况下, IBOutlet对象的类型都是retain的,由于这些对象来自界面文件(xib, storyboard),所以其出事化过程无需关心,其他处理方式与普通成员变量大体相同,只有一点,需要在-  (void) viewDidUnload函数中将这些变量置空, 如下所示:

[cpp] view plaincopy

  1. - (void) viewDidUnload  

  2. {  

  3.     self.outletA = nil;  

  4.     self.outletB = nil;  

  5.     [super viewDidUnload];  

  6. }  


当出现内存警告时,viewDidUnload将会被调用。前面提到过,self.outletA = nil 等价于[self setOutletA: nil], 所以,在出现内存警告的情况下,IBOutlet类型的对象会被释放。


三:容器对象与内存管理

iOS中,容器对象对其内的对象拥有所有权,也就是说,当一个对象被插入到容器内时,其retainCount会加一,从容器内移除时,其retainCount会减一,当容器本身被release时,期内所有对象的retainCount都会减一。如下代码所示:

[cpp] view plaincopy

  1. NSString* stringA = [[NSString alloc] init];//stringA的retainCount: 1  

  2. NSArray* array = [[NSArray alloc] init];  

  3. [array addObject: stringA];//stringA的retainCount:2  

  4. [stringA release];//stringA的retainCount:1  

  5. [array release];//retainCount: 0  


四:稀缺资源的管理

稀缺资源包括文件,网络连接,缓存等,这些资源是很关键的系统资源,系统内其他应用可能会随时需要这些资源,所以,这些资源就不适合作为类的成员变量了,因为dealloc的实际调用时间,是否真正调用是我们无法控制的,很有可能造成稀缺资源被无意义的占用,二其他应用却无法获得相应资源。所以,随时申请随时释放是最好的选择。


五:AutoRelease

简单说,autorelease对象的释放动作由AutoReleasePool完成,所有autorelease对象在其【对应】的AutoReleasePool释放的过程中,都会受到一条release消息,也就是说,pool析构的实际也就是autorelease对象析构的时机,注意,这里的【对应】指的是离改autorelese最近的那一个AutoRelasePool。

有这么几种情况必须自己创建AutoReleasePool:

1:程序没有界面,也就是没有消息循环的程序,

2:一个循环内创建大量临时的autorelease对象,那么写法最好是这样的:

[cpp] view plaincopy

  1. NSAutoReleasePool* outPool = [[NSAutoReleasePool alloc] init];  

  2. for (int index = 0; index != 1000000; ++index) {  

  3.     NSAutoReleasePool* pool = [[NSAutoReleasePool alloc] init];  

  4.     NSString* temp = [[[NSString alloc] init] autorelease];  

  5.     [pool drain];  

  6. }  

  7. [outPool release];  


若果没有循环中的pool, 那么直到结束循环之前,这1000000个autorelease 临时对象都不会被释放掉,占用大量内存。

3:线程函数内需要有AutoReleasePool对象,否则期内生成的autorelease对象在线程函数结束时不会被释放(此条对Cocoa-Touch不适用)。

4:普通函数返回对象,这里的普通函数指的是非alloc, new, copy, mutablecopy开头的函数,为了确保返回的对象有效,需要将返回的对象设为autorelease,如下所示:

[cpp] view plaincopy

  1. - (NSString*) returnString  

  2. {  

  3.     NSString* tempString = [[NSString alloc] init];  

  4.     tempString = @"autorelease";  

  5.     return [tempString autorelease];  

  6. }  


实际上,iOS SDK中绝大多数普通函数返回的都是autorelease对象。


六:其他注意事项

1:避免循环引用,如果两个对象互相为对方的成员变量,那么这两个对象一定不能同时为retain,否则,两个对象的dealloc函数形成死锁,两个对象都无法释放。

2:不要滥用autorelease,如果一个对象的生命周期很清晰,那最好在结束使用后马上调用release,过多的等待autorelease对象会给内存造成不必要的负担。

3:编码过程中,建议将内存相关的函数,如init, dealloc, viewdidload, viewdidunload等函数放在最前,这样比较显眼,忘记处理的概率也会降低。

4:AutoReleasePool对象在释放的过程中,在IOS下,release和drain没有区别。但为了一致性,还是推荐使用drain。


参考:Advanced Memory Management Programming Guide


本文转载自:http://blog.csdn.net/lifengzhong/article/details/7739496

木木情深
粉丝 37
博文 189
码字总数 26451
作品 0
广州
程序员
私信 提问
iOS 学习资料整理

视频教程(英文) Developing iOS 7 Apps for iPhone and iPad 斯坦福开放教程之一, 课程主要讲解了一些 iOS 开发工具和 API 以及 iOS SDK 的使用, 属于 iOS 基础视频 iPad and iPhone Applic...

拉偶有所依
2015/01/19
2.2K
4
iOS开发之Object-C和Cocoa [转]

Objective-C Objective-C,是扩充C的面向对象编程语言。是在C的基础上,加入面向对象特性扩充而成的编程语言。目前,Objective-C主要应用于Mac OS X和iOS开发,而在NeXTSTEP和OpenStep中它更...

ilscott
2012/12/13
0
0
为何提高iOS技能?

如题,我的困惑 去年11月份自学,今年3月份辞职正式转型做iOS工作,但是项目一直到6月份都是自己做,感觉提升很慢,算是个中级偏下水平 求教iOS朋友,你们是怎么提升自己的? 书单 《Effecti...

卧龙小
2015/09/18
1K
11
ArcGIS for iOS 开发系列(1) – 基本概念

1.1 iOS简介 2006年苹果公司发布了智能手机iPhone,卓越的外形设计和新颖的触摸式交互,令其迅速风靡全球,随后发布的平板电脑iPad同样也取得了巨大成功,二者所搭载的都是iOS智能移动操作系...

长平狐
2012/11/28
867
0
2018 iOS 面试题大全(补充完整版)

原文地址:2018 iOS 面试题大全 由于原作者并没有继续更新,这里我转过来继续更新下 这个栏目将持续更新--请iOS的小伙伴关注! 1、iOS 应用导航模式有哪些? 2、iOS 中持久化方式有哪些? 3、...

Theendisthebegi
2018/11/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Android 状态栏颜色兼容方案

一、需求 由于Android系统碎片化比较严重,因此为了统一调整状态栏颜色,因此实现一个工具类相当必要。 注意:本类支持Android 5.0 以上的版本,android 5.0之前的兼容性太差,因此不做处理。...

IamOkay
3分钟前
0
0
Kafka竟然不支持读写分离!今天才知道!

Kafka竟然不支持读写分离!今天才知道! 在 Kafka 中,生产者写入消息、消费者读取消息的操作都是与 leader 副本进行交互的,从 而实现的是一种主写主读的生产消费模型。数据库、Redis 等都具...

架构师springboot
5分钟前
0
0
如何让TCP服务器只accept指定IP地址的connect?

socket编程中,有一个TCP服务器,需要实现这样的一个功能,它只能接受某个指定IP的客户连接,其他IP地址的连接都拒绝。 比如,服务器在端口3344进行监听(listen),他只接受某个IP(如192.168...

shzwork
12分钟前
0
0
apache druid 迁移过程(hdfs)insert-segment-to-db

背景 由于公司内部调整,apahce druid 集群需迁移。深度存储的路径也需要调整。 迁移hdfs中的文件 这个我们有专门的人员迁移的,具体操作不详。应该就是借助工具,在hdfs中进行 segements的迁...

潘鸿
18分钟前
1
0
Promise本质

本质上,Promise是返回一个对象,你可以在上面添加回调函数,而不像普通的回调那样,向函数传送回调函数。 Promise提供的保证: 1、 Promise的作用: 1、将我们从深层内嵌回调中解脱出来 //...

器石_
22分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部