我们曾经对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