在iPhone应用中如何避免内存泄露

简介:   我们曾经对iPhone的内存管理做过比较深入的报道,如何有效控制iPhone内存管理的对象的所有权与引用计数和以及iPhone内存的自动释放与便捷方法。本文我们将介绍在iPhone应用中如何避免内存泄露。

  我们曾经对iPhone的内存管理做过比较深入的报道,如何有效控制iPhone内存管理的对象的所有权与引用计数和以及iPhone内存的自动释放与便捷方法。本文我们将介绍在iPhone应用中如何避免内存泄露。想了解“在iPhone应用中如何避免内存泄露”就必须先了解iPhone内存管理的所有权。

  关于所有权

  所有权是iPhone内存管理的核心思想,对象的所有者负责在使用完对象后进行释放。一个对象可以有多个所有者,当它没有所有者时将被设置为取消分配(deallocation)。

  创建对象时,所有权通过alloc、new、或者copy的方式建立,之后通过调用retain或者通过Cocoa函数来分配和复制对象的所有权。内存释放有两种方式,一种方法是明确地请求释放对象的所有权,另一种方法则是使用自动释放池(auto-release pool)。

  所有权的背后是一个和引用有关的运算系统,iPhone SDK的大多数对象使用这个系统,彼此之间建立着很强的引用和参照。

  当你创建一个对象时,引用值为1,调用一次retain则对象的引用值加1,调用一次release则对象的引用值减1,当引用值为0时,对象的所有权分配将被取消。使用自动释放池意味着对象的所有权将在一段延后的时间内被自动取消。

  对象之间也可以建立弱的引用参照,此时意味着,引用值不会被保留,对象的分配需要手动取消。

  什么时候使用retain?

  什么时候你想阻止对象在使用前就被释放?

  每当使用copy、alloc、retain、或者Cocoa函数来创建和复制所有权,你都需要相应的release或者auto-release。

  开发者应该从所有权的角度来考虑对象,而不必担心引用值。只要你有相应的retain和release方法,就能够对引用值进行+1和-1操作。

  注意:你或许想使用[object retainCount],但它可能因为SDK的底层代码而发生返回值出错的情况。在内存管理时不推荐这种方式。

  自动释放

  将对象设置为自动释放意味着不需要明确地请求释放,因为当自动释放池清空时它们将被自动释放。iPhone在主线程上运行自动释放池,能够在事件循环结束后释放对象。当你创建你自己的线程时,你需要创建自己的自动释放池。

  iPhone上有便利的构造函数,用这种方法创建的对象会设置为自动释放。

例子:

 
 
1 . NSString * str0 = @ " hello " ;
2 . NSString * str1 = [NSString stringWithString:@ " world " ];
3 . NSString * str2 = str1;

  一个已分配的对象可以用如下的方法设置为自动释放:

 
 
NSString * str = [[NSString alloc] initWithString:@ " the flash? " ];
[str autorelease];

  或者用下面的方法:

 
 
1 . NSString * str = [[[NSString alloc] initWithString:@ " batman! " ] autorelease];

  当指针出界,或者当自动释放池清空时,自动释放对象上的所有权将被取消。

  在一个事件循环结束时,自动释放池内的构件通常会被清空。但是当你的循环每次迭代都分配大量内存时,你或许希望这不要发生。这种情况下,你可以在循环内创建自动释放池。自动释放池可以嵌套,所以内部池清空时,其中分配的对象将被释放。在下面的例子中,每次迭代后将释放对象。

 
 
1 . for ( int i = 0 ; i < 10 ; ++ i)
2 . {
3 . NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
4 . NSString * str = [NSString stringWithString:@ " hello world " ];
5 . [self ProcessMessage: str];
6 . [pool drain];
7 . }

  注意:在编写的时候iPhone不支持垃圾回收,所以drain和release的功能相同。当你想为程序设置OSX的端口时通常会使用drain,除非后来在iPhone中添加了垃圾回收机制。Drain能够击发垃圾回收器释放内存。

  返回一个对象的指针

  开发者在遵循所有权规则时需要清楚哪些函数拥有对象的所有权。下面是返回一个对象的指针并释放的例子。

错误的方法:

 
 
1 . - (NSMutableString * ) GetOutput
2 . {
3 . NSMutableString * output = [[NSMutableString alloc] initWithString:@ " output " ];
4 . return output;
5 . }
6 . - ( void ) Test
7 . {
8 . NSMutableString * obj = [self GetOutput];
9 . NSLog(@ " count: %d " , [obj retainCount]);
10 . [obj release];
11 . }

  在这个例子中,output 的所有者是 GetOutput,让 Test 释放 obj 违反了Coccoa内存管理指南中的规则,尽管它不会泄露内存但是这样做不好,因为Test 不应该释放并非它所拥有的对象。

正确的方法:

 
 
- (NSMutableString * ) GetOutput
{
NSMutableString
* output = [[NSMutableString alloc] nitWithString:@ " output " ];
return [output autorelease];
}
- ( void ) Test
{
NSMutableString
* obj = [self GetOutput];
NSLog(@
" count: %d " , [obj retainCount]);
}

 

  在第二个例子中,output 被设置为当 GetOutput 返回时自动释放。output的引用值减少,GetObject 释放 output 的所有权。Test 函数现在可以自由的 retain 和 release 对象,请确保它不会泄露内存。

  例子中 obj 被设置为自动释放,所以 Test 函数没有它的所有权,但是如果它需要在其他地方存储对象会怎样?

此时对象需要有一个新的所有者来保留。

  Setters

  setter函数必须保留它所存储的对象,也就是声明所有权。如果我们想要创建一个 setter 函数,我们需要在分配一个新的指向成员变量的指针之前做两件事情。

在函数里:

 
 
- ( void ) setName:(NSString * )newName

  首先我们要减少成员变量的引用值:

 
 
[name release];

  这将允许当引用值为0时 name 对象被释放,但是它也允许对象的其他所有者继续使用对象。

  然后我们增加新的 NSString 对象的引用值:

 
 
[newName retain];

  所以当 setName 结束时, newName 不会被取消分配。 newName 现在指向的对象和 name 指向的对象不同,两者有不同的引用值。

  现在我们设置 name 指向 newName 对象:

 
 
name = newName;

 

  但是如果 name 和 newName 是同一个对象时怎么办?我们不能在它被释放后保留它,并再次释放。

  在释放存储的对象前保留新的对象:

 
 
1 . [newName retain];
2 . [name release];
3 . name = newName;

  现在两个对象是相同的,先增加它的引用值,然后再减少,从而使得赋值前引用值不变。

  另一种做法是使用 objective-c:

  声明如下:

 
 
@property(nonatomic, retain) NSString * name;

  1. nonatomic 表示没有对同一时间获取数据的多个线程进行组块儿。Atomic 为一个单一的线程锁定数据,但因为 atomic 的方式比较缓慢,所以不是必须的情况一般不使用。

  2. retain 表示我们想要保留 newName 对象。

  我们可以使用 copy代替 retain:

 
 
@property(nonatomic, copy) NSString * name;

  这和下面的函数一样:

 
 
1 . - ( void ) setName:(NSString * )newName
2 . {
3 . NSString * copiedName = [newName copy];
4 . [name release];
5 . name = copiedName;
6 . [name retain];
7 . [copiedName release];
8 . }

  newName 在这里被复制到 copiedName,现在 copiedName 拥有串的一个副本。name 被释放,而 copiedName 被赋给 name。之后 name 保留这个串,从而使得 copiedName 和 name 同时拥有它。最后 copiedName 释放这个对象,name 成为这个串的唯一所有者。

  如果我们有如下的函数,像这样的 setters 将被输入用来保留成员对象:

 
 
1 . - ( void ) Test
2 . {
3 . NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
4 . // do something...
5 . name = [self GetOutput];
6 . // do something else...
7 . NSLog(@ " Client Name before drain: %@ " , name);
8 . [pool drain];
9 . NSLog(@ " Client Name after drain: %@ " , name);
10 . }

  name 在调用至 drain 后是未定义的,因为当池被释放时,name 也将被释放。

  如果我们用如下的部分替代赋值:

 
 
[self setName:[self GetOutput]];

  然后 name 将被这个类所有,在使用时保留直到调用 release

  那么我们何时释放对象?

  由于 name 是成员变量,释放它的最安全的办法是对它所属的类使用 dealloc 函数。

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

  注意:虽然并不总是调用 dealloc,依靠 dealloc 来释放对象可能是危险,可能会触发一些想不到的事情。在出口处,iPhone OS 可能在调用 dealloc 前清空全部应用程序的内存。

  当用 setter 给对象赋值时,请小心下面的语句:

 
 
[self setName:[[NSString alloc] init]];

 

  name 的设置是正确的但 alloc 没有相应的释放,下面的方式要好一些:

 
 
1 . NSString * s = [[NSString alloc] init];
2 . [self setName:s];
3 . [s release];

  或者使用自动释放:

 
 
[self setName:[[[NSString alloc] init] autorelease]];

  自动释放池

  自动释放池释放位于分配和 drain 函数之间的对象。

  我们在下面的函数中设置一个循环,在循环中将 NSNumber 的一个副本赋给 magicNumber,另外将 magicNumber 设置为自动释放。在这个例子中,我们希望在每次迭代时清空自动释放池(这样可以在赋值的数量很大时节省循环的内存)

 
 
- ( void ) Test
{
NSString
* clientName = nil;
NSNumber
* magicNumber = nil;
for ( int i = 0 ; i < 10 ; ++ i)
{
NSAutoreleasePool
* pool = [[NSAutoreleasePool alloc] init];
magicNumber
= [[self GetMagicNumber] copy];
[magicNumber autorelease];
if (i == [magicNumber intValue])
{
clientName
= [self GetOutput];
}
[pool drain];
}
if (clientName != nil)
{
NSLog(@
" Client Name: %@ " , clientName);
}
}

  这里存在的问题是 clientName 在本地的自动释放池中被赋值和释放,所以当外部的池清空时,clientName 已经被释放了,任何对 clientName 的进一步使用都是没有定义的。

  在这个例子中,我们在赋值后保留 clientName,直到结束时再释放它:

 
 
1 . - ( void ) Test
2 . {
3 . NSString * clientName = nil;
4 . NSNumber * magicNumber = nil;
5 . for ( int i = 0 ; i < 10 ; ++ i)
6 . {
7 . NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
8 . magicNumber = [[self GetMagicNumber] copy];
9 . [magicNumber autorelease];
10 . if (i == [magicNumber intValue])
11 . {
12 . clientName = [self GetOutput];
13 . [clientName retain];
14 . }
15 . [pool drain];
16 . }
17 . if (clientName != nil)
18 . {
19 . NSLog(@ " Client Name: %@ " , clientName);
20 . [clientName release];
21 . }
22 . }

  我们在调用 retain 函数和 release 函数的期间获得 clientName 的所有权。通过添加一对 retain 和 release 的调用,我们就确保 clientName 在明确调用释放前不会被自动释放。

  集合

  当一个对象被添加进集合时,它就被集合所拥有。

  在这个例子中我们分配一个串,它现在有了所有者;

 
 
1 . NSString * str = [[NSString alloc] initWithString:@ " Bruce Wayne " ];

  然后我们将它添加进数组,现在它有两个所有者:

 
 
1 . [array addObject: str];

  我们可以安全的释放这个串,使其仅被数组所有:

 
 
[str release];

  当一个集合被释放时,其中的所有对象都将被释放。

 
 
1 . NSMutableArray * array = [[NSMutableArray alloc] init];
2 . NSString * str = [[NSString alloc] initWithString:@ " Bruce Wayne " ];
3 . [array addObject: str];
4 . [array release];

  在上面的例子中,我们分配了一个数组和一个串,然后将串添加到数组中并释放数组。这使得串仅拥有一个所有者,并且在我们调用 [str release] 前它不会被释放。

  用线程传递指针

  在这个函数中,我们从串的 input 传递到函数 DoSomething,然后释放 input

 
 
1 . - ( void ) Test
2 . {
3 . NSMutableString * input = [[NSMutableString alloc] initWithString:@ " batman! " ];
4 . [NSThread detachNewThreadSelector:@selector(DoSomething:) toTarget:self withObject:input];
5 . [input release];
6 . }

  detatchNewThreadSelector 增加 input 对象的引用值并在线程结束时释放它。这就是为什么我们能够在线程刚开始的时候就释放 input,而无论函数 DoSomething 何时开始或结束。

 
 
1 . - ( void ) DoSomething:(NSString * )str
2 . {
3 . [self performSelectorOnMainThread:@selector(FinishSomething:) withObject:str waitUntilDone: false ];
4 . }

  performSeclectorOnMainThread 也会保留传递的对象,直到 selector 结束。

  自动释放池是特殊的线程,所以如果我们在一个新的线程上创建自动释放的对象,我们需要创建一个自动释放池来释放它们。

 
 
[NSThread detachNewThreadSelector:@selector(Process) toTarget:self withObject:nil];

  这里在另一个线程上调用函数 Process

 
 
1 . - ( void ) Process
2 . {
3 . NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
4 . NSMutableString * output = [[[NSMutableString alloc] initWithString:@ " batman! " ] autorelease];
5 . NSLog(@ " output: %@ " , output);
6 . [self performSelectorOnMainThread:@selector(FinishProcess) withObject:nil waitUntilDone: false ];
7 . [pool drain];
8 . }

  对象 output 被分配并且在自动释放池中设置了自动释放,它将在函数结束前被释放。

 
 
1 . - ( void ) FinishProcess
2 . {
3 . NSMutableString * output = [[[NSMutableString alloc] initWithString:@ " superman? " ] autorelease];
4 . NSLog(@ " output: %@ " , output);
5 . }

  系统会为主线程自动创建一个自动释放池,所以在 FinishProcess 中,我们不需要为主线程上运行的函数创建自动释放池。

  总结

  为了在你的iPhone中避免内存泄露,你必须要清楚每个被分配对象的所有者是谁,要明白什么时候释放所有权,并且还要始终按对设置 retain 和 release,这三点非常重要。如果你遵循所有权的规则,你的应用将更加稳定并且因为 bug 的减少而节省大量时间。

目录
相关文章
|
2月前
|
编解码 测试技术 开发工具
测试 iPhone 应用在不同屏幕尺寸和分辨率下的响应式效果
【10月更文挑战第23天】测试 iPhone 应用在不同屏幕尺寸和分辨率下的响应式效果是确保应用质量和用户体验的重要环节。通过手动测试、自动化测试、视觉效果评估、性能测试、用户体验测试等多种方法的综合运用,能够全面地发现应用在响应式效果方面存在的问题,并及时进行解决和优化。同时,持续的测试和优化也是不断提升应用质量和用户满意度的关键。
|
2月前
|
监控 JavaScript 算法
如何使用内存监控工具来定位和解决Node.js应用中的性能问题?
总之,利用内存监控工具结合代码分析和业务理解,能够逐步定位和解决 Node.js 应用中的性能问题,提高应用的运行效率和稳定性。需要耐心和细致地进行排查和优化,不断提升应用的性能表现。
196 77
|
2月前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
143 62
|
2月前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
80 31
|
1月前
|
开发框架 .NET PHP
网站应用项目如何选择阿里云服务器实例规格+内存+CPU+带宽+操作系统等配置
对于使用阿里云服务器的搭建网站的用户来说,面对众多可选的实例规格和配置选项,我们应该如何做出最佳选择,以最大化业务效益并控制成本,成为大家比较关注的问题,如果实例、内存、CPU、带宽等配置选择不合适,可能会影响到自己业务在云服务器上的计算性能及后期运营状况,本文将详细解析企业在搭建网站应用项目时选购阿里云服务器应考虑的一些因素,以供参考。
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
76 1
|
2月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
56 3
|
2月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
2月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
2月前
|
编解码 iOS开发 UED
响应式设计在 iPhone 开发适配中的具体应用
【10月更文挑战第23天】响应式设计在 iPhone 开发适配中扮演着至关重要的角色,它能够帮助我们打造出适应不同屏幕尺寸和用户需求的高质量应用。通过合理运用响应式设计的原则和方法,我们可以在提供良好用户体验的同时,提高开发效率和应用的可维护性。