`
woodn_z
  • 浏览: 44239 次
  • 性别: Icon_minigender_1
  • 来自: 无锡
文章分类
社区版块
存档分类
最新评论

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;

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

   1. NSString* str = [[NSString alloc] initWithString:@"the flash?"]; 
   2. [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 不应该释放并非它所拥有的对象。

正确的方法:

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

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

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

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

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

在函数里:

   1. - (void) setName:(NSString*)newName

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

   1. [name release];

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

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

   1. [newName retain];

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

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

   1. name = newName;

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

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

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

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

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

声明如下:

   1. @property(nonatomic, retain) NSString *name;

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

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

我们可以使用 copy 代替 retain:

   1. @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 也将被释放。

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

   1. [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 给对象赋值时,请小心下面的语句:

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

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

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

或者使用自动释放:

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

自动释放池

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

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

   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.            } 
  14.            [pool drain]; 
  15.    } 
  16.    if (clientName != nil) 
  17.    { 
  18.            NSLog(@"Client Name: %@", clientName); 
  19.    } 
  20. } 
  21. 

这里存在的问题是 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];

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

   1. [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 结束。

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

   1. [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 的减少而节省大量时间。
分享到:
评论

相关推荐

    ios iphone开发-内存管理

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

    iPhone内存管理

    详细且全面地讲述了iPhone开发中的内存管理技术

    iphone开发官方指南-内存管理编程指南

    有大大弄成docx格式,我转换成pdf格式再次上传

    移动互联网开发笔记 VOL 2

    刊首语 征稿 在Android平台下定制系统的主屏幕 IPhone开发内存管理  Three20的使用 iOS开发者计划申请流程攻略 雅虎画报IPad版本的开发经验介绍

    iPhone 应用开发中Object-C 内存管理--千锋培训

    文档介绍了简介,基本概念,函数,获得所有权的函数包括,释放所有权的函数包括,规则,容器,其他所有权的产生,,循环引用

    iPhone开发基础教程

    Objective-C是扩展C的面向对象编程语言,也是iPhone开发用到的主要语言。. 本书结合理论知识与示例程序,全面而系统地讲述Objective-C编程的相关内容,包括Objective-C在C的基础上引入的特性和Cocoa工具包的功能及...

    iphone开发入门经典源码

    3.3 l 声明变量 3.3.2 分配、初始和释放对象 3.3 13使用方法及发送消息 3.3.4 表达式和决策 3.4 内存管理 34.1 释放对象 3.4.2 使用autorclease方法 3.4.3 保留对象 3.4.4 在dealloc中释放实例变量 34.5 释放规则 ...

    Objective-C高级编程 iOS与OS X多线程和内存管理

    帮助学习关于OC中内存管理的知识点

    IPhone开发常用技术笔记汇总

    本压缩包中包含了Iphone开发中常用到的技术总结笔记,五六十中技术方法以及季节方案,包括内存管理,方法回调,获取当前地点,自定义CELL,VIew圆角等等等,太多的奶水包,是我开发中所有的精华所在,只有你不知道的...

    iphone开发笔记

    退回输入键盘 2 CGRect 2 CGPoint & CGSize 3 设置透明度 3 设置背景色 3 自定义颜色 3 竖屏 3 横屏 3 状态栏高 3 导航栏、工具栏高 3 ...Objective-C内存管理 44 iphone更改键盘右下角按键的type 45

    《iPhone开发实战》.(Christopher Allen).pdf

     本书适合所有 iphone开发人员学习参考。... 目录 第一部分 iphone编程简介. 第1章 iphone简介2 1.1 iphone核心规范3 1.1.1 iphone的输入及输出规范3 1.1.2 iphone网络规范4 1.1.3 iphone浏览器规范5...

    iphone开发进阶

    iphone开发进阶 简要介绍iphone os的4个主要部分组成、OBJC的内存管理、归档、容器; 定制UIButton、视图切换、屏幕的触摸事件检测、使用SQLLite连接数据库等等

    object c/iphone 开发 试题

    16.内存管理 ①:程序A里有一段内存被成功申请完成之后,内存计数器就从0变为1 (这个过程是alloc); ②:然后程序B里也要使用这个内存,那么内存计数器从1变为2 (这个过程是retain); ③:紧接着程序A不需要这个内存...

    iPhone开发秘籍.part2.rar

    1.8.3 关于示例代码和内存管理的 注意事项.....18 1.9 构建Hello World 应用程序.....19 1.9.1 创建iPhone 项目.....19 1.9.2 运行主干.....20 1.9.3 定制iPhone 项目.....20 1.9.4 编辑标识信息.....21 1.9.5 使用...

    iPhone开发秘籍.part4.rar

    1.8.3 关于示例代码和内存管理的 注意事项.....18 1.9 构建Hello World 应用程序.....19 1.9.1 创建iPhone 项目.....19 1.9.2 运行主干.....20 1.9.3 定制iPhone 项目.....20 1.9.4 编辑标识信息.....21 1.9.5 使用...

    iPhone开发秘籍.part1.rar

    1.8.3 关于示例代码和内存管理的 注意事项.....18 1.9 构建Hello World 应用程序.....19 1.9.1 创建iPhone 项目.....19 1.9.2 运行主干.....20 1.9.3 定制iPhone 项目.....20 1.9.4 编辑标识信息.....21 1.9.5 使用...

    iphone3开发基础教程

    4.9 成为出色的内存使用者 67 4.10 小结 68 第5章 自动旋转和自动调整大小 70 5.1 使用自动调整属性处理旋转 71 5.1.1 指定旋转支持 71 5.1.2 使用自动调整属性设计界面 73 5.1.3 自动调整属性 74 5.1.4 设置按钮的...

Global site tag (gtag.js) - Google Analytics