from:http://blog.163.com/lyzaily@126/blog/static/42438837200933015825871/
For a comprehensive coverage of this topic, consult "Hold Me, Use Me, Free me", but for convenience here is a summary of the major points.
Newcomers to Cocoa often seem intent on making life difficult for themselves when it comes to memory management. Following a few simple rules can make life easy. Failure to adhere to the rules will almost certainly lead at some point to memory leaks, or runtime exceptions due to messages being sent to freed objects.
Cocoa does not use garbage collection. You must do your own memory management through reference counting, using -retain
, -release
and -autorelease
.
Method | Description |
---|---|
-retain |
increases the reference count of an object by 1 |
-release |
decreases the reference count of an object by 1 |
-autorelease |
decreases the reference count of an object by 1 at some stage in the future |
-alloc |
allocates memory for an object, and returns it with retain count of 1 |
-copy |
makes a copy of an object, and returns it with retain count of 1 |
-copy
, -alloc
and -retain
should equal the use of -release
and -autorelease
.-dealloc
method to release the instance variables you own-alloc
/ -release
- (void)printHello { NSString *string; string = [[NSString alloc] initWithString:@"Hello"]; NSLog(string); // we created string with alloc -- release it [string release]; }
- (void)printHello { NSString *string; string = [NSString stringWithFormat:@"Hello"]; NSLog(string); // we created string with convenience constructor // we can assume it's autoreleased }
Sometimes it might seem tedious or pedantic, but if you use accessor methods consistently the chances of problems with memory management will decrease considerably.
If you are using -retain
and -release
on a classes instance variables throughout your code, you're almost certainly doing the wrong thing.
Consider a Counter object whose count we want to set.
To get and set the count we should define two accessor methods:
Notice that the names of the accessor methods follow a pattern: -variablename
and -setVariablename
.
Following these conventions will make your code more readable, and, more importantly, allow you at a later stage to use key-value coding, should you wish (see the NSKeyValueCoding
protocol).
Since we have an object instance variable we must also implement a dealloc method:
Supposing we want to implement a method to reset the counter. We have a couple of choices. In the first we've used a convenience constructor, so we assume the new number is autoreleased. We have no need for any retain or release messages.
However, if we create the NSNumber instance with -alloc
, we must balance that with a -release
.
The following will almost certainly work correctly for simple cases, but tempting as it may be to eschew accessor methods, this will also almost certainly lead to a mistake at some stage.
- (void)reset { NSNumber *zero = [[NSNumber alloc] initWithInt:0]; [count release] count = zero; }
- (void)reset { NSNumber *zero = [[NSNumber alloc] initWithInt:0]; [self setCount:zero]; }
The retain count of the new number is 1 (from alloc) and we have not sent a -release
within the scope of the method. The number is unlikely ever to be freed, which will result in a memory leak.
-release
- (void)reset { NSNumber *zero = [NSNumber numberWithInt:0]; [self setCount:zero]; [zero release]; }
This will fail the first time you subsequently access count. The convenience constructor method returns an autoreleased object, so you don't have to send another release. Doing so will mean that when the release due to autorelease is sent, it will reduce the retain count to 0, and the object will be freed. When you next access count you will be sending a message to a freed object (typically you'll get a SIGBUS 10 error).
When an object is added to an array, dictionary, or set (etc), the collection retains it. It will be sent a corresponding release message when the collection is itself released. Thus if you want for example to create an array of numbers, you might do either of the following:
In this case there is no need to retain the new numbers (n), since the array will do so.
In this case you need to send n a -release
message within the scope of the for
loop to balance the retain count of 1 set in -alloc
. Since the array retained the number when it was added by -addObject:
, it will not be released while it's in the array.
To understand this, put yourself in the position of the person who implemented the collection class. You want to make sure that no objects you're given to look after disappear out from under you, so you send them a -retain
message as they're passed in. If they're removed, you have to send a balancing -release
message, and any remaining objects should be sent a -release
message during your own -dealloc
method.
翻译:
当Cocoa新手在进行内存管理时,他们看上去总是把事情变得更为复杂。遵循几个简单的规则就可以把生活变得更简单。而不遵循这些规则,他们几乎一定会造成诸如内存泄露或者将消息发送给释放掉的对象而出现的的运行错误。
Cocoa不使用垃圾回收(当然,Objective-C 2.0之后开始就使用了),你必须通过计算reference的数量进行自己的内存管理,使用-retain,-release和-autorelease。
-retain
将一个对象的reference数量增加1。
-release
将一个对象的reference数量减少1。
-autorelease
在未来某些时候将reference数量减少1.
-alloc
为一个对象分配内存,并设置保留值数量(retain count)为1。
-copy
复制一个对象,并将其做为返回值。同时设置保留值数量(retain count)为1。
1 在一定的代码段中,使用-copy,-alloc和-retain的次数应该和-release,-autorelease保持一致。
2 使用便利构造方法创建的对象(比如NSString的stringWithString)可以被认为会被自动释放。(autoreleased)
3 在使用你自己的参数实例时,需要实现-dealloc方法来释放。
-alloc / -release
- (void)printHello
{
NSString *string;
string = [[NSString alloc] initWithString:@"Hello"];
NSLog(string);
// 我们用 alloc 创建了NSString,那么需要释放它
[string release];
}
- (void)printHello
{
NSString *string;
string = [NSString stringWithFormat:@"Hello"];
NSLog(string);
// 我们用便利构造方法创建的NSString
//我们可以认为它会被自动释放
}
虽然有时候你可能会认为这很麻烦,但是如果你始终使用了存取方法,造成内存管理问题的麻烦将会降低很多。
如果你在代码实例的参数中频繁使用-retain和-release,几乎可以肯定你做了错误的事情。
假设我们希望设置一个Counter对象的数量值。
@interface Counter : NSObject
{
NSNumber *count;
}
为了获取和设置count值,我们定义两个存取方法:
- (NSNumber *)count
{
return count;
// 无需retain或者release,
// 仅仅传递数值
}
- (void)setCount:(NSNumber *)newCount
{
// newCount值会被自动释放,那么我们希望保留这个newCount
// 所以需要在这里retain。
[newCount retain];
// 由于我们在这个方法中仅仅改变了计算数量的对象,我们可以在这里先释放它。因为[nil release]在objective-c中也是允许的,所以即使count值没有被指定,也可以这样调用。
//我们必须在[newCount retain]之后再释放count,因为有可能这两个对象的指针是同一个。我们不希望不小心释放它。
[count release];
// 重新指定
count = newCount;
}
注意存取方法的命名约定遵循一个模式: -参数名 和 -set参数名。
遵循这一约定,会使你的代码可读性更强,而且,更重要地是你可以在后面使用key-value编码。(参阅NSKeyValueCoding协议)。
由于我们有一个对象实例参数,我们必须实现一个释放方法:
- (void)dealloc
{
[self setCount:nil];
[super dealloc];
}
假设我们希望实现一个方法重置计数器,我们会有很多选择。在最开始,我们使用了一个 便利构造方法,所以我们假设新的数值是自动释放的。我们不需要发送任何retain或者release消息。
- (void)reset
{
NSNumber *zero = [NSNumber numberWithInt:0];
[self setCount:zero];
}
然而,如果我们使用-alloc方法建立的NSNumber实例,那我们必须同时使用一个-release。
- (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[self setCount:zero];
[zero release];
}
在简单的情况下,以下代码几乎一定可以正常运行,但是由于可能没有使用存取方法,下面的代码在某些情况下几乎一定会出问题。
错误-没有使用存取方法
- (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[count release]
count = zero;
}
错误-实例泄露
- (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[self setCount:zero];
}
新建的NSNumber数值数量是1(通过alloc),而我们在这个方法里没有发出-release消息。那么这个NSNumber就永远不会被释放了,这样就会造成内存泄露。
错误-对已经释放的实例发送-release消息
- (void)reset
{
NSNumber *zero = [NSNumber numberWithInt:0];
[self setCount:zero];
[zero release];
}
你随后在存取count的时候在这里就会出错。这个简便构造方法会返回一个自动释放的对象,你无需发送其他释放消息。
这样写代码意味着,由于对象已经被自动释放,那么当你释放时,retain count将被减至0,对象已经不存在了。当你下次希望获取count值时,你的消息会发到一个不存在的对象(通常这样你会得到一个SIGBUS 10的错误提示)。
当对象被加入到数组、字典或者集合中,集合类会将其保留。当集合被释放的同时,对象也会收到一个释放消息。如果你希望写一个建立数字数组的例子,你可能会这么写:
NSMutableArray *array;
int i;
// …
for (i = 0; i < 10; i++)
{
NSNumber *n = [NSNumber numberWithInt: i];
[array addObject: n];
}
在这个例子里,你无需保留新建的数值,因为数组会帮你保留。
NSMutableArray *array;
int i;
// …
for (i = 0; i < 10; i++)
{
NSNumber *n = [[NSNumber alloc] initWithInt: i];
[array addObject: n];
[n release];
}
本例中,在for循环里你需要给n发送一个-release消息,因为你需要始终在-alloc之后将n的数量保持为1。这么做的原因是当其通过-addObject:方法被添加至数组中时,数组已经将其保存起来。即使你释放了n,但是这个数字由于已经保存在数组里,所以不会被释放。
为了了解这些,假设你自己就是编写数组类的人。你不希望接收的对象未经你同意就消失,所以你会在对象传递进来时,对其发送一个-retain消息。如果他们被删除,你同时也要对应地发送一个-release消息。在你自己-dealloc时,你也要给你收到的所有对象发送一个-release。