“.NET研究”在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];

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

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

上海徐汇企业网站设计与制作e>
上海网站建设 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上海闵行企业网站设计与制作/span>. 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 detach上海闵行企业网站制作NewThreadSelector:@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 的减少而节省大量时间。

目录
相关文章
|
3月前
|
存储 Shell Linux
快速上手基于 BaGet 的脚本自动化构建 .net 应用打包
本文介绍了如何使用脚本自动化构建 `.net` 应用的 `nuget` 包并推送到指定服务仓库。首先概述了 `BaGet`——一个开源、轻量级且高性能的 `NuGet` 服务器,支持多种存储后端及配置选项。接着详细描述了 `BaGet` 的安装、配置及使用方法,并提供了 `PowerShell` 和 `Bash` 脚本实例,用于自动化推送 `.nupkg` 文件。最后总结了 `BaGet` 的优势及其在实际部署中的便捷性。
181 10
|
1月前
|
编解码 测试技术 开发工具
测试 iPhone 应用在不同屏幕尺寸和分辨率下的响应式效果
【10月更文挑战第23天】测试 iPhone 应用在不同屏幕尺寸和分辨率下的响应式效果是确保应用质量和用户体验的重要环节。通过手动测试、自动化测试、视觉效果评估、性能测试、用户体验测试等多种方法的综合运用,能够全面地发现应用在响应式效果方面存在的问题,并及时进行解决和优化。同时,持续的测试和优化也是不断提升应用质量和用户满意度的关键。
|
1月前
|
开发框架 监控 .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
|
1月前
|
编解码 iOS开发 UED
响应式设计在 iPhone 开发适配中的具体应用
【10月更文挑战第23天】响应式设计在 iPhone 开发适配中扮演着至关重要的角色,它能够帮助我们打造出适应不同屏幕尺寸和用户需求的高质量应用。通过合理运用响应式设计的原则和方法,我们可以在提供良好用户体验的同时,提高开发效率和应用的可维护性。
|
1月前
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
102 2
|
2月前
|
数据库连接 开发者
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第15天】在.NET中,有两种有效的资源释放方式:一是使用`using`语句,适用于实现`IDisposable`接口的对象,如文件流、数据库连接等,能确保资源及时释放,避免泄漏;二是手动调用`Dispose`方法并处理异常,提供更灵活的资源管理方式,适用于复杂场景。这两种方式都能有效管理资源,提高应用性能和稳定性。
|
2月前
|
算法 Java 数据库连接
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第14天】在 .NET 中,`IDisposable` 接口提供了一种标准机制来释放非托管资源,如文件句柄、数据库连接等。此类资源需手动释放以避免泄漏。实现 `IDisposable` 的类可通过 `Dispose` 方法释放资源。使用 `using` 语句可确保资源自动释放。此外,.NET 的垃圾回收器会自动回收托管对象所占内存,提高程序效率。示例代码展示了如何使用 `MyFileHandler` 类处理文件操作并释放 `FileStream` 资源。
|
3月前
|
数据采集 JSON API
.NET 3.5 中 HttpWebRequest 的核心用法及应用
【9月更文挑战第7天】在.NET 3.5环境下,HttpWebRequest 类是处理HTTP请求的一个核心组件,它封装了HTTP协议的细节,使得开发者可以方便地发送HTTP请求并接收响应。本文将详细介绍HttpWebRequest的核心用法及其实战应用。
164 6
|
3月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
280 1