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

   创建对象时,所有权通过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 中,我们不需要为主线程上运行的函数创建自动释放池。

时间: 2024-10-25 03:24:03

如何避免iPhone应用中内存泄露的相关文章

控制台和MFC中内存泄露工具vld的使用

   最近想检测下项目中内存泄露的情况,选中了vld这款.在查找使用方法的时候,大都是控制台下的示例,添加到main函数所在的源文件上.换成MFC就纠结了,不知道添加到哪里去.本文记录控制台和MFC中的使用vld过程.    vld资源:    1).大家可以移步下边的网址下载:     http://vld.codeplex.com/releases/view/82311    2).也可以到我的资源中下载:     http://download.csdn.net/detail/alex_m

Android中内存泄露代码优化及检测

  内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况.内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费.         正如下文所说,内存泄漏与许多其他问题有着相似的症状,并且通常情况下只能由那些可以获得程序源代码的程序员才可以分析出来.然而,有不少人习惯于把任何不需要的内存使用的增加描述为内存泄漏,即使严格意义上来说这是不准确的. 一.内存泄露         内存泄漏会因为减少可

避免 Android中Context引起的内存泄露_Android

Context是我们在编写Android程序经常使用到的对象,意思为上下文对象. 常用的有Activity的Context还是有Application的Context.Activity用来展示活动界面,包含了很多的视图,而视图又含有图片,文字等资源.在Android中内存泄露很容易出现,而持有很多对象内存占用的Activity更加容易出现内存泄露,开发者需要特别注意这个问题. 本文讲介绍Android中Context,更具体的说是Activity内存泄露的情况,以及如何避免Activity内存泄

避免 Android中Context引起的内存泄露

Context是我们在编写Android程序经常使用到的对象,意思为上下文对象. 常用的有Activity的Context还是有Application的Context.Activity用来展示活动界面,包含了很多的视图,而视图又含有图片,文字等资源.在Android中内存泄露很容易出现,而持有很多对象内存占用的Activity更加容易出现内存泄露,开发者需要特别注意这个问题. 本文讲介绍Android中Context,更具体的说是Activity内存泄露的情况,以及如何避免Activity内存泄

java造成内存泄露原因

一.Java内存回收机制 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址.Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的.GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请.引用.被引用.赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题.在J

java内存泄露是错误还是异常

问题描述 今天去掏宝面试,被问到"java的内存泄露是错误还是异常?"我答:是错误我不知道答得对不对?请大家说说!! 解决方案 解决方案二:个人愚见:如果内存泄露的消息被java反馈回来,并有警示消息那么应该是异常,应该说所谓"异常"就是被程序捕获的错误,超出程序预期的目的或者计划.如果内存泄露后,未能捕获进而引发了灾难性的后果,例如主程序崩溃或者蓝屏死机,那么就是应该是错误.能否捕获并处理是区分是否是错误或者异常的关键,这个题目感觉出的很模糊,java的内存泄露到

聊聊内存泄露

一.Java内存回收机制  不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址.Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的.GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请.引用.被引用.赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题.在

Node.js中内存泄漏分析

内存泄漏(Memory Leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况.如果内存泄漏的位置比较关键,那么随着处理的进行可能持有越来越多的无用内存,这些无用的内存变多会引起服务器响应速度变慢,严重的情况下导致内存达到某个极限(可能是进程的上限,如 v8 的上限:也可能是系统可提供的内存上限)会使得应用程序崩溃. 传统的 C/C++ 中存在野指针,对象用完之后未释放等情况导致的内存泄漏.而在使用虚拟机执行的语言中如 Java.JavaScript 由于使用了 GC (Garbag

再谈java的内存泄露

这两天看了一本老书<bitter java>,第一次系统地了解了所谓"反模式".就书的内容来说已经过于陈旧,书中提到的magic servlet.复合jsp等等反模式已经是早就熟知的编程禁忌,而如web页面不能有太多元素这样的反模式也因为ajax的出现(异步加载)变的不是那么"反模式"了,其中又讲述了很多ejb的反模式,这些在轻量级框架流行的今天也早已经过时.不过书中有一个章节倒是挺有价值,讲述的是java的内存泄露问题,我认为是我目前读的关于这方面问题