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上有便利的构造函数,用这种方法创建的对象会设置为自动释放。
例子:
NSString* str0 = @"hello";
NSString* str1 = [NSString stringWithString:@"world"];
NSString* str2 = str1;
一个已分配的对象可以用如下的方法设置为自动释放:
NSString* str = [[NSString alloc] initWithString:@"the flash?"];
[str autorelease];
或者用下面的方法:
NSString* str = [[[NSString alloc] initWithString:@"batman!"] autorelease];
当指针出界,或者当自动释放池清空时,自动释放对象上的所有权将被取消。
在一个事件循环结束时,自动释放池内的构件通常会被清空。但是当你的循环每次迭代都分配大量内存时,你或许希望这不要发生。这种情况下,你可以在循 环内创建自动释放池。自动释放池可以嵌套,所以内部池清空时,其中分配的对象将被释放。在下面的例子中,每次迭代后将释放对象。
for (int i = 0; i < 10; ++i)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString* str = [NSString stringWithString:@"hello world"];
[self ProcessMessage: str];
[pool drain];
}
注意:在编写的时候iPhone不支持垃圾回收,所以drain和release的功能相同。当你想为程序设置OSX的端口时通常会使用drain,除非后来在iPhone中添加了垃圾回收机制。Drain能够击发垃圾回收器释放内存。
返回一个对象的指针
开发者在遵循所有权规则时需要清楚哪些函数拥有对象的所有权。下面是返回一个对象的指针并释放的例子。
错误的方法:
- (NSMutableString*) GetOutput
{
NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
return output;
}
- (void) Test
{
NSMutableString* obj = [self GetOutput];
NSLog(@"count: %d", [obj retainCount]);
[obj release];
}
在这个例子中,output 的所有者是 GetOutput,让 Test 释放 obj 违反了Coccoa内存管理指南中的规则,尽管它不会泄露内存但是这样做不好,因为Test 不应该释放并非它所拥有的对象。
正确的方法:
- (NSMutableString*) GetOutput
{
NSMutableString* output = [[NSMutableString alloc] initWithString:@"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 是同一个对象时怎么办?我们不能在它被释放后保留它,并再次释放。
在释放存储的对象前保留新的对象:
[newName retain];
[name release];
name = newName;
现在两个对象是相同的,先增加它的引用值,然后再减少,从而使得赋值前引用值不变。
另一种做法是使用 objective-c:
声明如下:
@property(nonatomic, retain) NSString *name;
1. nonatomic 表示没有对同一时间获取数据的多个线程进行组块儿。Atomic 为一个单一的线程锁定数据,但因为 atomic 的方式比较缓慢,所以不是必须的情况一般不使用。
2. retain 表示我们想要保留 newName 对象。
我们可以使用 copy 代替 retain:
@property(nonatomic, copy) NSString *name;
这和下面的函数一样:
- (void) setName:(NSString*)newName
{
NSString* copiedName = [newName copy];
[name release];
name = copiedName;
[name retain];
[copiedName release];
}
newName 在这里被复制到 copiedName,现在 copiedName 拥有串的一个副本。name 被释放,而 copiedName 被赋给 name。之后 name 保留这个串,从而使得 copiedName 和 name 同时拥有它。最后 copiedName 释放这个对象,name 成为这个串的唯一所有者。
如果我们有如下的函数,像这样的 setters 将被输入用来保留成员对象:
- (void) Test
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
name = [self GetOutput];
// do something else...
NSLog(@"Client Name before drain: %@", name);
[pool drain];
NSLog(@"Client Name after drain: %@", name);
}
name 在调用至 drain 后是未定义的,因为当池被释放时,name 也将被释放。
如果我们用如下的部分替代赋值:
[self setName:[self GetOutput]];
然后 name 将被这个类所有,在使用时保留直到调用 release
那么我们何时释放对象?
由于 name 是成员变量,释放它的最安全的办法是对它所属的类使用 dealloc 函数。
- (void)dealloc
{
[name release];
[super dealloc];
}
注意:虽然并不总是调用 dealloc,依靠 dealloc 来释放对象可能是危险,可能会触发一些想不到的事情。在出口处,iPhone OS 可能在调用 dealloc 前清空全部应用程序的内存。
当用 setter 给对象赋值时,请小心下面的语句:
[self setName:[[NSString alloc] init]];
name 的设置是正确的但 alloc 没有相应的释放,下面的方式要好一些:
NSString* s = [[NSString alloc] init];
[self setName:s];
[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,直到结束时再释放它:
- (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];
[clientName retain];
}
[pool drain];
}
if (clientName != nil)
{
NSLog(@"Client Name: %@", clientName);
[clientName release];
}
}
我们在调用 retain 函数和 release 函数的期间获得 clientName 的所有权。通过添加一对 retain 和 release 的调用,我们就确保 clientName 在明确调用释放前不会被自动释放。
集合
当一个对象被添加进集合时,它就被集合所拥有。在这个例子中我们分配一个串,它现在有了所有者;
NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];
然后我们将它添加进数组,现在它有两个所有者:
[array addObject: str];
我们可以安全的释放这个串,使其仅被数组所有:
[str release];
当一个集合被释放时,其中的所有对象都将被释放。
NSMutableArray* array = [[NSMutableArray alloc] init];
NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];
[array addObject: str];
[array release];
在上面的例子中,我们分配了一个数组和一个串,然后将串添加到数组中并释放数组。这使得串仅拥有一个所有者,并且在我们调用 [str release] 前它不会被释放。
用线程传递指针
在这个函数中,我们从串的 input 传递到函数 DoSomething,然后释放 input
- (void) Test
{
NSMutableString* input = [[NSMutableString alloc] initWithString:@"batman!"];
[NSThread detachNewThreadSelector:@selector(DoSomething:) toTarget:self withObject:input];
[input release];
}
detatchNewThreadSelector 增加 input 对象的引用值并在线程结束时释放它。这就是为什么我们能够在线程刚开始的时候就释放 input,而无论函数 DoSomething 何时开始或结束。
- (void) DoSomething:(NSString*)str
{
[self performSelectorOnMainThread:@selector(FinishSomething:) withObject:str waitUntilDone:false];
}
performSeclectorOnMainThread 也会保留传递的对象,直到 selector 结束。
自动释放池是特殊的线程,所以如果我们在一个新的线程上创建自动释放的对象,我们需要创建一个自动释放池来释放它们。
[NSThread detachNewThreadSelector:@selector(Process) toTarget:self withObject:nil];
这里在另一个线程上调用函数 Process
- (void) Process
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableString* output = [[[NSMutableString alloc] initWithString:@"batman!"] autorelease];
NSLog(@"output: %@", output);
[self performSelectorOnMainThread:@selector(FinishProcess) withObject:nil waitUntilDone:false];
[pool drain];
}
对象 output 被分配并且在自动释放池中设置了自动释放,它将在函数结束前被释放。
- (void) FinishProcess
{
NSMutableString* output = [[[NSMutableString alloc] initWithString:@"superman?"] autorelease];
NSLog(@"output: %@", output);
}
系统会为主线程自动创建一个自动释放池,所以在 FinishProcess 中,我们不需要为主线程上运行的函数创建自动释放池。
总之:
为了在你的iPhone中避免内存泄露,你必须要清楚每个被分配对象的所有者是谁,要明白什么时候释放所有权,并且还要始终按对设置 retain 和 release,这三点非常重要。如果你遵循所有权的规则,你的应用将更加稳定并且因为 bug 的减少而节省大量时间。
iPhone应用中如何避免内存泄露?的更多相关文章
- Java宝典(四)------Java中也存在内存泄露。
--Java中会存在内存泄露吗? --如果你想当然的以为Java里有了垃圾回收机制就不会存在内存泄露,那你就错了. Java里也会存在内存泄露! 我们慢慢来分析. 所谓内存泄露就是指一个不再被程序使用 ...
- 对开发中常见的内存泄露,GDI泄露进行检测
对开发中常见的内存泄露,GDI泄露进行检测 一.GDI泄露检测方法: 在软件测试阶段,可以通过procexp.exe 工具,或是通过任务管理器中选择GDI对象来查看软件GDI的对象是使用情况. 注意点 ...
- java: web应用中不经意的内存泄露
前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...
- 谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用
其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题.一.什么是内存泄露(memory leak)?内存泄露不是指内存坏了,也不是指内存没插稳漏出来 ...
- jQuery在updatepanel中使用造成内存泄露
wijmo用户反馈了一个RadialGauge控件内存泄露的bug,采用chrome监控内存使用情况,发现明显的内存泄露,在前面的文章中我就发现了jQuery内存泄露的问题,这次再次发现此问题,自然就 ...
- 浅谈C++中的那些内存泄露
尽管学过C语言.可是C++里面的一些基础还是不太懂,还须要再掌握. 老范也開始要讲C++设计模式了,必须快点看了.不然就要白花窝滴钱了. 对于内存泄露,我的个人理解就是程序在执行过程中,自己开辟了空间 ...
- .NET中常见的内存泄露问题——GC、委托事件和弱引用
一.什么是内存泄露(memory leak)? 内存泄露不是指内存坏了,也不是指内存没插稳漏出来了,简单来说,内存泄露就是在你期待的时间内你程序所占用的内存没有按照你想象中的那样被释放. 因此什么是你 ...
- Android中如何查找内存泄露
1.首先确定是否有内存泄露及哪个程序造成. 1.1.内存泄露已弹出out of memory对话框的情况. 这种情况很简单,直接看对话框就知道是哪个应用的问题了.然后再分析该应用是否是因为内存泄露造成 ...
- 多线程编程中使用pthread_create内存泄露问题
//tls5源代码: #include <stdio.h> #include <unistd.h> #include <string.h> #include &qu ...
随机推荐
- 【转】 NSString什么时候用copy,什么时候用strong
原文: http://blog.csdn.net/itianyi/article/details/9018567 大部分的时候NSString的属性都是copy,那copy与strong的情况下到底有 ...
- 内容替换Filter
有时候需要对网站进行控制,防止输出非法内容或者敏感信息.这时我们可以使用filter来进行内容替换,其工作原理为,在Servlet将内容输出到response时,response将内容缓存起来,在Fi ...
- Spring与Jdbc Demo
方法一:继承JdbcTemplate来实现 1.配置applicationContext <!-- 获取数据源连接 dbcp --> <bean id="dataSourc ...
- C++函数二义性问题,我怎么感觉编译器有偷懒嫌疑!!!
瞎扯一段,讲得不一定对.纯属学习! struct BB{ void a(){ cout << "bb's a()\n"; }}; struct B1 : public ...
- 【POJ3243】【拓展BSGS】Clever Y
Description Little Y finds there is a very interesting formula in mathematics: XY mod Z = K Given X, ...
- linux 监控服务器脚本
#!/bin/bash ctime=`date +%x%T`monitor_dir=/home/jk/if [ ! -d $monitor_dir ]; then mkdir $monitor_ ...
- 关于Redis的知识汇总[转]
1. Overview 1.1 资料 <The Little Redis Book> ,最好的入门小册子,可以先于一切文档之前看,免费. 作者Antirez的博客,Antirez维护的Re ...
- 1s延时程序
#include <reg52.h>sbit P1_0 = P1^0;void Delay(); // 下面引用时一定要和这里的大小写一致否则会有警告或错误 void Main(){whi ...
- C# 当double数值较大且小数位过多时转化成字符串并保留小数位
今天在C#中碰到了一个问题,需要将double转换成字符串显示,要求保留小数位. 在网上查询了一下相关的文章 具体如下: double temp=3.1415926; (F)Fixed point:s ...
- android中使用intent来实现Activity带数据跳转
大家都知道startActivity()是用来切换跳转Activity的.如果想要在另个Activity中出书数据的话.只需要在源activity中使用intent.putExtra()方法传出数据. ...