iOS内存管理策略和实践

摘要:
未使用变量名称,因此名称不需要发送发布消息。delloc的作用是释放对象占用的内存并处理自己拥有的资源,包括释放自己的变量。不要在dealloc的最后一行调用父类的dealloc。不要尝试管理系统资源。当应用程序终止时,可能不会调用对象的解除分配。因为进程内存是自动清除和退出的,所以操作系统清理资源比调用所有内存管理方法更有效。CoreFoundation使用相似但不同的规则。CoreFoundation对象使用类似的内存管理规则,但Cocoa和CoreFoundation的命名管理不同。

转:http://www.cocoachina.com/applenews/devnews/2013/1126/7418.html

内存管理策略(memory Management Policy)
NSObject protocol中定义的的方法和标准命名惯例一起提供了一个引用计数环境,内存管理的基本模式处于这个环境中。NSObject类定义了一个方法叫dealloc,当对象销毁的时候,dealloc会被自动调用。本文描述,在Cocoa中所有正确管理内存基本规则,并提供了一些使用正确的例子。
 
【基本的内存管理规则】
内存管理模式基于对象的“所有权”上。任何对象都会被有一个或多个使用者引用,只要对象还有一个使用者,该对象就应该继续存在。如果一个对象没有使用者了,系统将自动销毁它。为了让开发者清晰的了解:使用对象和不再使用对象的场景,Cocoa设置了以下策略:
1.管好自己创建的对象。开发者使用alloc、new、copy和mutableCopy来创建对象。
 
2.使用retain来获得对象的所有权。某个函数接受的对象,通常保证在该函数调用期间仍然可用,并可以安全返回对象给上层调用者。开发者在以下两种情况下使用retain
1)在“访问函数”(accessor)的实现中或者在init方法,为了将对象作为自己的属性。
2)防止对象被其他操作释放掉,从而变为无效的对象。
 
3.当你不在需要的时候,必须放弃对象所有权。
 
一个简单的例子
看看下面的代码段,可以证明刚刚的所说的策略
  1. {   
  2.     Person *aPerson = [[Person alloc] init];   
  3.     // ...   
  4.     NSString *name = aPerson.fullName;   
  5.     // ...   
  6.     [aPerson release];   
  7. }   
Person被通过alloc创建之后,当Person不在使用的时候,发送了一个release的消息。name这个变量没有使用,所以name不必发送release消息。
 
使用autorelease来发送一个延迟的release
典型的使用场景:函数返回一个对象的时候。例如,你可以像这样实现fullName的方法:
  1. - (NSString *)fullName {   
  2.     NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",   
  3.                                           self.firstName, self.lastName] autorelease];   
  4.     return string;   
  5. }   
上面就是典型的场景:你想放弃对象的所有权,但是又想让调用者在string销毁前使用返回值。
 
还可以通过下面的实现达到上面的效果:
  1. - (NSString *)fullName {   
  2.     NSString *string = [NSString stringWithFormat:@"%@ %@",   
  3.                                  self.firstName, self.lastName];   
  4.     return string;   
  5. }   
根据命名惯例,full name方法不具备返回值的所有权。因此,调用者无需对返回值string进行release
 
开发者不应该获得“通过引用传递的对象”的所有权
Cocoa中的一些方法,指定是传递引用。例如NSError对象包涵错误的信息,比如:initWithContentsOfURL:options:error: (NSData) and initWithContentsOfFile:encoding:error: (NSString).这种情况,之前的规则中已经描述过了。你调用这些方法,但是没有创建NSError对象,所以,你没有它的所有权。因此不用release,比如:
  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放弃对象的所有权】
NSObject类定义了一个方法dealloc,当某个对象没有使用者,并它的内存是可再生的,delloc就自动被调用。delloc的角色就是释放对象占用的内存并且处理自己所拥有的资源,包括本身变量的释放。
下面的代码展示了,如何实现Person类的dealloc函数。
  1. @interface Person : NSObject   
  2. @property (retain) NSString *firstName;   
  3. @property (retain) NSString *lastName;   
  4. @property (assign, readonly) NSString *fullName;   
  5. @end   
  6.     
  7. @implementation Person   
  8. // ...   
  9. - (void)dealloc   
  10.     [_firstName release];   
  11.     [_lastName release];   
  12.     [super dealloc];   
  13. }   
  14. @end   
 
【重要】
任何时候,不要直接调用某一对象的dealloc。
不许在dealloc的最后一行调用父类的dealloc
不要尝试管理系统资源。(参考内存管理实践)
应用程序终止的时候,对象的dealloc可能不会被调用。因为进程的内存是自动清除退出,让操作系统清理资源比调用所有的内存管理方法更有效地。
 
Core Foundation使用相似的却又不同的规则
Core Foundation对象使用类似的内存管理规则(查看Memory Management Programming Guide for Core Foundation),但是Cocoa和Core Foundation的命名管理并不相同。尤其是,Core Foundation的Create Rule(在Memory Management Programming Guide for Core Foundation中查看“The Create Rule” )并不适用于返回Objective-C对象的方法。比如以下代码片段:
  1. MyClass *myInstance = [MyClass createInstance]; 
 
原文:Memory Management Policy
 

 内存管理实践

尽管基本的概念在“内存管理策略”文章中简单得阐述了,但是还有一些实用的步骤让你更容易管理内存;有助于确保你的程序最大限度地减少资源需求的同时,保持可靠和强大。

使用“访问器方法”让内存管理更简单

假如,你的程序有一个对象类型的属性,你必须保证:当你使用的时候,任何的已经赋值了的对象不会被销毁。被赋新值的时候,开发者必须获得对象的所有权,并放弃正在使用对象的所有权。

有时候,这些听起来很老套和繁琐,如果开发者统一使用访问器方法,内存管理有问题的机会大大减少。如果开发者在代码中总是使用retain和release管理实例变量,几乎肯定会做错事,换句话说:访问器是必须的。

来看一个Counter对象

  1. @interface Counter : NSObject   
  2. @property (nonatomic, retain) NSNumber *count;   
  3. @end;   

该属性声明了两个访问器。典型的做法,开发者告诉编译器合成(synthesize)访问器方法。了解访问器是如何实现的对开发者是有好处的。

在get访问器中,开发者只需要返回变量即可,不需要retain和release

  1. - (NSNumber *)count {   
  2.     return _count;   
  3. }   

在set访问器方法里,如果每一位开发者都能按照相同的规则,对新的count进行负责,开发者必须通过retain来获得对象的所有权。开发者也需要通过release来放弃旧对象的所有权。(在Objective-c中给对象发送nil消息是允许的,即是_count没有被设置,仍然是安全的。)为了防止两个对象是相同的,开发者需要先调用[newCount retain](开发者可不希望意外的把对象给销毁)

  1. - (void)setCount:(NSNumber *)newCount {   
  2.     [newCount retain];// retain new object first;   
  3.     [_count release];   
  4.     // Make the new assignment.   
  5.     _count = newCount;   
  6. }   

【使用访问器方法设置属性值】

假设你想实现一个重置counter的方法。你有多个选择。

第一种方式:用alloc创建NSNumber实例,然后可以用release释放。

  1. - (void)reset {   
  2.     NSNumber *zero = [[NSNumber alloc] initWithInteger:0];   
  3.     [self setCount:zero];   
  4.     [zero release];   
  5. }   

第二种方法:用便捷构造方法创建NSNumber对象。因此,不用调用retain和release等。

  1. - (void)reset {   
  2.     NSNumber *zero = [NSNumber numberWithInteger:0];   
  3.     [self setCount:zero];   
  4. }   

注意:以上两种方法都使用访问器方法。

下面将几乎可以肯定,正常的情况下,因为它可能避开访问器方法,这样很诱人。这样做几乎肯定会导致一个错误在某些时候。(比如,当开发者忘记retain或者release,或者将来内存管理机制有变化)

  1. - (void)reset {   
  2.     NSNumber *zero = [[NSNumber alloc] initWithInteger:0];   
  3.     [_count release];   
  4.     _count = zero;   
  5. }   

 注意:如果开发者使用key-value observing,上面这种方法也不属于KVO范畴

不要在初始化和dealloc函数中使用访问器方法

唯一不让使用访问器的地方就是initializer和dealloc。为了将counter初始化为零,开发者可以这样实现:

  1. - init {   
  2.     self = [super init];   
  3.     if (self) {   
  4.         _count = [[NSNumber alloc] initWithInteger:0];   
  5.     }   
  6.     return self;   
  7. }   

为了初始化为非零数据,可以这么实现initWithCount方法:

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

既然Counter类有一个类属性,开发者还需要实现dealloc,dealloc通过发送release,将放弃左右对象的所有权,最终dealloc还会调用父类的dealloc函数。

  1. - (void)dealloc {   
  2.     [_count release];   
  3.     [super dealloc];   
  4. }   

使用弱引用(Weak References)来避免循环retain

对一个对象retain操作是强引用(strong reference)。所有强引用都被release之后对象才被销毁。如果,两个对象有彼此的强引用,就出现众所周知的问题——循环retain。(包括:直接,或通过其他对象强引用链的情况)

对象的关系如图所示,有一个潜在的循环retain。Document对象每一页都有一个Page对象。每个Page对象有一个paragraphs属性,表明该Page在那一Document中。如果Document中的Page是强引用,Page类中的paragraphs属性也是强引用,那个对象都不会被销毁。Document的引用值直到Page对象被释放才变为0,而Page对象直到Document释放才被释放。

iOS内存管理策略和实践第1张

解决循环引用的的方法是使用弱引用。弱引用是一种非占有所有权的关系,不对源对象retain,只是引用(reference)。然后,为了保持对象图的完整性,强引用还是必要的(如果只有弱引用,pages和paragraphs将没有任何的所有者,也就不能被释放)Cocoa形成了一个惯例:父对象应该强引用子对象,子变量应该弱引用父对象。所以,在图1中,Document对象强引用page对象,page对象弱引用Document对象。

Cocoa中的弱引用例子包含了(但不限于)table data sources, outline view items, notification observers, and miscellaneous targets and delegates.

开发者给弱引用对象发送消息应小心一些。如果给一个已经销毁的对象发消息,程序将crash。当对象可用的时候,开发者需具备良好的定义条件(You must have well-defined conditions for when the object is valid.)。

大多数情况:弱引用对象知道其他对象弱引用了自己,当自己被销毁的时候,有责任通知其他对象。比如,当开发者用notification center注册一个对象,notification center存储一个弱引用对象,并发送post消息给对象。当对象已经被销毁了。

开发者需从notification center中注销对象,防止notification center发送消息给不存在的对象。同样,当一个delegate对象被销毁后,开发者嘘移除delegate通过发送一个参数为nil的setDelegate消息而这些消息通常从对象的dealloc中发送。

避免销毁正在使用的对象

Cocoa's所有权策略指定接受的对象应该,保证在函数调用范围可用;还可以返回接受的对象,而不用担心被release掉。保证从程序中的gerrer方法中返回实例变量或者计算后的值是没问题的。

问题是,当需要的时候对象仍然有效。偶尔有些例外,主要是下面两种情况:

1.从基础集合类中移除对象

  1. heisenObject = [array objectAtIndex:n];   
  2. [array removeObjectAtIndex:n];   
  3. // heisenObject could now be invalid.   

当对象从基本集合类移除,集合类发送一个release(而不是autorelease)消息。如果集合类是对象的唯一拥有者,被移除的对象(例子中heisenObject)就被立即销毁。

2.父对象被销毁

  1. id parent = <#create a parent object#>;   
  2. // ...   
  3. heisenObject = [parent child] ;   
  4. [parent release]; // Or, for example: self.parent = nil;   
  5. // heisenObject could now be invalid.   

 某些情况,开发者从其他对象获得一个对象,直接或见解释放父对象。release父对象导致被销毁,父对象又是子对象的唯一拥有者,子对象将同时被销毁。

防止这些情况,当开发者收到heisenObject时retain,使用完release掉,比如:

  1. heisenObject = [[array objectAtIndex:n] retain];   
  2. [array removeObjectAtIndex:n];   
  3. // Use heisenObject...   
  4. [heisenObject release];   

不要对稀缺资源进行dealloc

不要在dealloc函数中管理file descriptor、network connections、buffer和caches这些资源。通常,开发者不应设计带有dealloc这样的类。dealloc可能延迟调用,要么就成为程序的一个bug或者造成程序崩溃。

相反,如果你有一个稀缺资源的类,你应该这样设计应用程序,例如,你知道当你不再需要的资源的时候,然后可以告诉实例clean up。通常,你会再释放该实例,紧接着调用dealloc,你不会受到额外的问题。

如果您尝试在dealloc中背驮式得资源管理,可能会出现问题。

1.顺序依赖被拆散。虽然开发者可能希望得到一个特定顺序,被拆散的对象图本质上是无序的。如果对象是被autorelease的,被拆散的顺序可能还有变化,也可能导致意想不到的结果。

2.非回收式的稀缺资源。内存泄露是可以修复bug,内存泄露的伤害不是立即致命的。如果稀缺资源在不能释放的时候,被你释放了,你可能会碰到更严重的问题。如果你的应用程序使用文件描述符(file descriptor),可能导致不能写数据。

3.在错误的线程中执行cleanup逻辑。如果一个对象被开发者设置为是autorelease的,它会被任意一个“它正好存在于的”线程的自动释放池给释放掉。这是很容易的致命错误:该资源应该在一个线程中使用和释放。

(If an object is autoreleased at an unexpected time, it will be deallocated on whatever thread’s autorelease pool block it happens to be in. This can easily be fatal for resources that should only be touched from one thread)

集合拥有它所包含的对象

当你添加一个对象到集合(如,array,dictionary和set),集合获得对象的所有权。对象被移除的时候或者集合本身release的时候,放弃对象的所有权。如果开发者想创建一个带有粒度的array,可以这么搞:

  1. NSMutableArray *array = <#Get a mutable array#>;   
  2. NSUInteger i;   
  3. // ...   
  4. for (i = 0; i < 10; i++) {   
  5.     NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];   
  6.     [array addObject:convenienceNumber];   
  7. }   

这种情况,开发者没有调用alloc,所以无需掉用release。也没有必要retain新的convenienceNumber。

  1. NSMutableArray *array = <#Get a mutable array#>;   
  2. NSUInteger i;   
  3. // ...   
  4. for (i = 0; i < 10; i++) {   
  5.     NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];   
  6.     [array addObject:allocedNumber];   
  7.     [allocedNumber release];   
  8. }   

这个情况,开发者需要向allocedNumber发送一个release消息在for的作用域之内,来匹配alloc。既然array的addObject方法 retain了allocedNumber,allocedNumber就会被array管理删除。

要像理解这些,把自己当做设计集合类的开发者们。你想保证:即使不在你照看下,集合中的变量能仍然好用,所以,当对象传入的时候,你retain了一次。当对象移除集合类,你需要发送release消息。

对象所有权策略是基于引用计数实现的

对象所有权的策略是通过引用计数——通常叫做retain count实现的。每一个对象有一个retaincount变量。

1.创建对象后,它的retaincount是1

2. retain之后,retain count +1

3.release之后 retain count -1 

4.autorelease之后,在自动释放池最后-1

5.对象的retain count减少到0的时候,对象被销毁。

【重要】

不要显式调用对象的retainCount,结果往往具有误导性,作为开发者可能不了解框架式如何对对象retain的。在调试内存管理中,你应该只关注确保你的代码遵循所有权规则。

原文:Practical Memory Management


关于iOS内存管理

应用程序内存管理是:“程序运行时,开辟的内存空间。使用它,释放它”的过程,写的好的程序尽可能少使用内存。在Objective-C中,内存管理被看做是:“在很多数据、代码下,分配受限内存资源所有权方法”。当你依据这个指南完成你的程序时,你将获得“通过显式管理对象的命周期,不使用的时候释放他们,来管理程序内存”的知识。

尽管,典型的内存管理是作用于单个对象,你的目标是通过管理对象图。你想确保:在内存中没有比实际需要的还多的对象。

iOS内存管理策略和实践第2张

概述

Objective-C提供两种内存管理的方法:

1.“manual-release”(MRR),需要显式管理内存通过跟踪对象的所有权。MRR基于NSObject类在运行时提供的引用计数实现的。

2.“Automatic Reference Counting”ARC,系统使用相同的引用计数基于MRR,但是在编译时,为开发者适当插入一些内存管理方法。强烈建议开发者在新项目中使用ARC。使用ARC就无需理解本文所描述的内容了。

防止内存相关问题的好的做法

两个主要的内存管理误用问题

1.释放或覆盖正在使用的数据。这将造成内存损坏,造成应用程序崩溃,或者更坏的情况是损坏用户数据。

2.没有释放数据,导致内存泄露。泄漏导致应用程序的内存使用量逐渐增加,这反过来又可能会导致系统性能较差或者应用程序被终止(crash)

引用计数内存管理的角度思考,但是,往往是适得其反,因为你往往会考虑内存管理方面的实现细则,而不是在你的实际目标。相反,你应该想到的内存管理对象所有权和对象图的角度。

1.Cocoa使用简单的命名惯例来指示,是否拥有函数返回的对象。(点击查看内存管理策略

2.尽管内存管理基本策略很简单,有一些实际的步骤,你可以使内存管理更轻松,有助于确保你的程序仍然可靠和稳定的,而在同一时间最大限度地减少资源需求。(点击查看内存管理实践

3.Autorelease pool 提供一种机制:让对象延迟release。这个对你想放弃所有权,但又想避免立即释放(比如函数的返回值)。有些时候,你可能会使用自己的autorelease池块。(点击查看自动释放池)。

使用分析工具来调试内存问题

在编译时候找出代码的问题。使用Xcode内嵌的Clang Static Analyzer 。

如果内存管理的问题仍然发生,还有其他的工具和技术,你可以用它来识别和诊断问题。

1.多数工具和技术都在TN2239中有描述,iOS Debugging Magic 特别是NSZombie来帮助找到过度释放对象。

2.使用Instruments来追踪引用计数事件并找到内存泄露。( 参考 Collecting Data on Your App

原文:About Memory Management

免责声明:文章转载自《iOS内存管理策略和实践》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【转载】make: Nothing to be done for `all'. 解决方法服务器上Ubuntu系统安装下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

IOS沙盒(SandBox)

IOS中的沙盒机制(SandBox)是一种安全体系,它规定了应用程序只能在为该应用创建的文件夹内读取文件,不可以访问其他地方的内容。所有的非代码文件都保存在这个地方,比如图片、声音、属性列表和文本文件等。 1.每个应用程序都在自己的沙盒内 2.不能随意跨越自己的沙盒去访问别的应用程序沙盒的内容 3.应用程序向外请求或接收数据都需要经过权限认证 查看模拟器的...

Linux进程地址空间之初探:一

引言:现代操作系统提供了一种对内存的抽象概念,叫做虚拟存储器,它为每个进程提供了一个大的,一致的,和私有的地址空间。通过一个很清晰的机制,虚拟存储器提供了3个重要的能力: 1)它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效的使用了主存。 2)它为每个进程提供了一致的...

ios 开发之 各种数据类型之间转换 (持续补充)

1.图片转base64串及反转回图片 UIImage图片转成base64字符串: 1 UIImage *_originImage = [UIImage imageNamed:@"full_playlist_hl.png"]; 2 NSData *_data = UIImageJPEGRepresentation(_image, 1.0f);3 NSStri...

iOS开发网络数据之AFNetworking使用

http网络库是集XML解析,Json解析,网络图片下载,plist解析,数据流请求操作,上传,下载,缓存等网络众多功能于一身的强大的类库。最新版本支持session,xctool单元测试。网络获取数据一直是手机软件的重中之重,如果处理的不好,会造成很差的用户体验。随着ASIHTTPRequest的停止更新,更换网络库是必然的事情,AFNetworking...

Linux-3.14.12内存管理笔记【构建内存管理框架(1)】

传统的计算机结构中,整个物理内存都是一条线上的,CPU访问整个内存空间所需要的时间都是相同的。这种内存结构被称之为UMA(Uniform Memory Architecture,一致存储结构)。但是随着计算机的发展,一些新型的服务器结构中,尤其是多CPU的情况下,物理内存空间的访问就难以控制所需的时间相同了。在多CPU的环境下,系统只有一条总线,有多个CP...

Linux内存描述之内存节点node--Linux内存管理(二)

1 内存节点node 1.1 为什么要用node来描述内存 这点前面是说的很明白了, NUMA结构下, 每个处理器CPU与一个本地内存直接相连, 而不同处理器之前则通过总线进行进一步的连接, 因此相对于任何一个CPU访问本地内存的速度比访问远程内存的速度要快 Linux适用于各种不同的体系结构, 而不同体系结构在内存管理方面的差别很大. 因此linux内核...