大家好,本文是文档翻译计划的第 02 篇,原文是:Memory Management Policy
一组定义在 NSObject protocol 的方法,以及一种标准方法命名约定,提供了在引用计数环境下的内存管理的基本模型。NSObject 这个类定义了名为 dealloc 的方法,当对象被释放的时候会被调用。本文介绍了在 Cocoa 项目中正确管理内存的一些基本规则,并提供一些正确的使用例子。
内存管理基于对一个对象的所有权,一个对象可以被多个对象引用。只要一个对象被引用着,就会一直存在不被释放;如果一个对象不再被引用,则会被系统自动回收释放。为了确保引用关系更加简单明确,Cocoa 规定了一些原则:
对自己创建的对象有引用
通过以方法名以 “alloc”, “new”, “copy”, or “mutableCopy” 开头的方法创建的对象。
通过 retain 可以引用一个对象
以函数参数形式传入的对象,在函数执行完毕之前都不会被销毁,该函数也可以放心的将该对象作为结果返回给调用者。
你可以在下面两个场景下使用 retain:① 在 getter 方法中或者 init 方法中使用 retain 来持有一个对象。② 防止其他操作的副作用引起正在使用的对象被释放,具体参考:Avoid Causing Deallocation of Objects You’re Using
当你不再需要某个对象的时候,需要放弃对其的引用
你可以使用 release 或 autorelease 来放弃对一个对象的引用。在 Cocoa 术语中,引用通常被称为 “releasing” 对象。
不要放弃一个你没拥有的对象
这只是上面明确提到的规则的推论。
[译者注] 内存管理的原则,一句话概括就是「谁创建的,谁释放」。这种规则最简单,谁让对象的引用计数加一,谁就负责在不再使用该对象的时候把引用计数减一。其中,引用计数加一用 retain,引用计数减一用 release 或者 autorelease。
为了说明上面提到的规则,请看下面这个例子。
{
Person *aPerson = [[Person alloc] init];
// ...
NSString *name = aPerson.fullName;
// ...
[aPerson release];
}
aPerson 这个对象创建的时候用的 alloc,所以后面在不在用到这个对象的时候调用了 release。name 这个对象调用的方法不是以 alloc 等关键字开头,所以不持有,所以不需要调用 release。
使用 autorelease 发送延迟的 release 操作,特别是在函数中返回一个对象的时候。比如你可以这么来实现 fullname 函数。
- (NSString *)fullName {
NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
self.firstName, self.lastName] autorelease];
return string;
}
你通过 alloc 创建一个对象,所以你有它的引用。为了遵循内存管理原则,你需要在不再需要改对象时释放对该对象的引用。如果你使用 release,则这个字符串在返回给调用者之前就已经被销毁了(这个函数返回了一个非法的对象)。使用 autorelease,你标明你想释放对这个字符串的引用,但是是延迟释放,这样的话调用者就可以拿到该对象了。
[译者注] autorelease 依赖自动释放池,调用了 autorelease,则该对象的引用计数不会立刻减一,而是等到代码所在的最近的一个自动释放池结束的时候才会被减一,进而对象才会被释放。关于自动释放池的内容,后面也会有相关的文章。
你也可以这么实现 fullname 方法。
- (NSString *)fullName {
NSString *string = [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
return string;
}
根据上面提到的基本规则,创建 string 这个对象的时候,用到的函数不是以 alloc 等开头,所以你并不拥有对改对象的引用,所以直接返回对象即可。
作为对比,下面的实现是错误的。
- (NSString *)fullName {
NSString *string = [[NSString alloc] initWithFormat:@"%@ %@",
self.firstName, self.lastName];
return string;
}
根据命名规则,调用者并没有拥有对函数返回的字符串的引用,所以调用者也不会 release 改字符串,因此该字符串对象泄漏了。
在 Cocoa 的一些方法中,指定通过引用返回一个对象(参数是 ClassName ** 或者 id *)。常见的模式是使用NSError对象,该对象包含发生错误时有关错误的信息。
当你执行这些方法时,你并没有拥有通过引用返回的对象,所以也不需要调用 release 释放它。用下面这个例子说明一下:
NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
// Deal with error...
}
// ...
[string release];
[译者注] 通过二级指针来获取一个对象,在 Cocoa 中很多地方有这种用法,最经典的就是上面例子中的 initWithContentsOfFile,通过二级指针很方便的取回一个对象。但是这种获得对象的方式并不符合上面提到的引用计数相关的原则,所以这里其实是增加了一种规则。
NSObject 类定义了 dealloc 方法,当一个对象的引用计数为 0 的时候并且内存被回收时,会被调用 dealloc 方法。dealloc 的作用是释放自己的内存、丢弃对其他对象或资源的引用。
下面的例子说明了你应该如何实现 Person 这个类的 dealloc 方法:
@interface Person : NSObject
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (assign, readonly) NSString *fullName;
@end
@implementation Person
// ...
- (void)dealloc
[_firstName release];
[_lastName release];
[super dealloc];
}
@end
Important:
Core Foundation 对象的内存管理规则也有很多相似之处(参考:Memory Management Programming Guide for Core Foundation)。但是 Cocoa 和 Core Foundation 的命名约定不一样。特别是创建对象的规则(参考:The Create Rule)不适用于返回 Objective-C 对象的方法。例如下面的代码片段,你不需要考虑 myInstance 的持有和释放问题。
MyClass *myInstance = [MyClass createInstance];