iPhone内存管理详细解说系列转载来源:http://keywind.me/blog/2011/05/07/iphonenei-cun-guan-li-xiang-xi-jie-shuo-yi/ 感谢这位作者
做iPhone开发内存管理是避免不了的问题,而且Apple不对iOS进行垃圾回收机制肯定有他的原因.要想清楚了解内存管理,官方文档是最好的资源,于是把这几天看的Memory Management Programming Guide进行整理,一方面做记录,一方面供大家做参考,一些地方翻译的不好还请留言指出.
相对于Mac OS X v10.5+,iOS不支持垃圾回收机制; 相对于普通系统复杂的内存管理,cocoa定义了一些规则来使其变得简单.
基本规则为: 你只能release或autorelease你拥有的object.这句话可具体为:
以下两条规则从上面那条延伸而来:
一个object可以有一个或更多的拥有者,只要这个object还有至少一个拥有者,它就不被销毁.Cocoa设定以下四条规则:
例子:
{
Thingamajig *myThingamajig = [[Thingamajig alloc] init];
// ...
NSArray *sprockets = [myThingamajig sprockets];
// ...
[myThingamajig release];
}
你用alloc创建myThingamejig,所以要用release释放,而sprockets并不是你创建的,所以不必释放.
所有权政策的底层实现靠的是retain counts,每个object都有retain count. 创建一个object,retain count=1; retain这个object,retain count+1; release这个object,retain count-1; autorelease这个object,retain count在未来某个时候-1; 要注意的是,不要可以去查看retainCount的值来判断是否释放,只会误导你,没有意义.
Autorelease有Autorelease Pools实现,看例子:
– (NSArray *)sprockets {
NSArray *array = [[NSArray alloc] initWithObjects:mainSprocket,
auxiliarySprocket, nil];
return [array autorelease];
}
这个是正确的写法,你alloc了array,但由于你要返回array给这个方法的调用者,所以不能马上释放,又不能不释放,所以有延迟的autorelease就成了最佳选择.而如果:
– (NSArray *)sprockets {
NSArray *array = [[NSArray alloc] initWithObjects:mainSprocket,
auxiliarySprocket, nil];
return array;
}
由于array是局部变量,这样你就释放不掉它了.再看:
– (NSArray *)sprockets {
NSArray *array = [[NSArray alloc] initWithObjects:mainSprocket,
auxiliarySprocket, nil];
[array release];
return array; // array is invalid here
}
由于array已经被释放了,所以也就不能返回给这个方法的调用者了,再看:
– (NSArray *)sprockets {
NSArray *array = [NSArray arrayWithObjects:mainSprocket,
auxiliarySprocket, nil];
return array;
}
你没有所有权,所以不要你释放.
共享的objects既要保证在调用的函数被保持有效,也要保证该函数在把objects return出去的时候因为release而变得无效了.简而言之就是保持objects在你需要的时候保持有效. 要注意两种意外:
(1)当一个object是从集合类(NAArray,NADictionary等等)中移除的:
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.
当一个object是从集合类中移除的,它就被发送一个release,如果集合类是它唯一的拥有者,那么这个object就被释放了.
(2)当一个object的父object被释放:
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.
当你从object1派生出object2,然后把object1释放,如果object1是object2唯一的拥有者,那么object2也将被释放 为了防止以上两种情况发生,你可以retain一下使拥有拥有权,然后不再需要时释放,比如
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// use heisenObject.
[heisenObject release];
如果你的类的有一个实例变量是一个object,你需要保证这个object的任何属性在你这个使用这个object时没有被释放.因此需要声明拥有权. 比如你的object允许它的MainSprocket属性被设置,你可以这样实现:
– (void)setMainSprocket:(Sprocket *)newSprocket {
[mainSprocket autorelease];
mainSprocket = [newSprocket retain]; /* Claim the new Sprocket. */
return;
}
需要把之前的mainSprocket先释放,值得注意的是,如果object1调用setMainSprocket,参数是object2的MainSprocket,也就是两者现在共享一个MainSprocket,此时如果object2的MainSprocket改变,object1的也跟着改变.如果你不想这样,你可以:
– (void)setMainSprocket:(Sprocket *)newSprocket {
[mainSprocket autorelease];
mainSprocket = [newSprocket copy]; /* Make a private copy. */
return;
}
这还有个问题,由于以上两个方法都是把原先的MainSprocket给autorelease了,但如果前后两个sprocket是一样的,当这个sprocket被release了那么你再retain或者copy就错了,这时你可以:
– (void)setMainSprocket:(Sprocket *)newSprocket {
if (mainSprocket != newSprocket) {
[mainSprocket release];
mainSprocket = [newSprocket retain]; /* Or copy, if appropriate. */
}
}
当一个object的retain count等于0时,它的dealloc方法被调用,用来释放它占有的内存,和其他资源,包括其他实例变量或者说object的拥有权,比如以下的实现释放了mainSprocket和auxiliarySprocket的所有权:
- (void)dealloc {
[mainSprocket release];
[auxiliarySprocket release];
[super dealloc];
}
一些方法指定一个object是以引用形式被返回的,(如ClassName * 或 id ),举一些例子:
initWithContentsOfURL:options:error: (NSData)
initWithContentsOfFile:encoding:error: (NSString)
executeFetchRequest:error: (NSManagedObjectContext)
这是因为你没有所有权,所以不必释放,例如:
NSString *fileName = <#Get a file name#>;
NSError *error = nil;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
// deal with error ...
}
// ...
[string release];
先看图:
以上现象中Document创建一个Page,Page追踪它在哪个Document,如果Document去retain它的Page,而Page也去retain它所在的Document,那么我们知道两者成循环永远释放不掉. 此时解决办法是Document去retain它的page,而Page引用它所在的Document,这被称为”弱引用”.
可以简单理解为强引用是拥有所有权,而弱引用没有.一些Cocoa典型的弱引用包括:table data sources, outline view items, notification observers, miscellaneous targets 和 delegates. 比如:NSTableView的object不会去retain它的data source,NSApplication不会去retain它的delegate. 你需要注意的是,发送消息给一个你只持有弱引用且已被销毁的object会使程序崩溃,一般条件下被弱引用的object在被销毁时需要通知弱引用的持有者,比如:当你在notification center注册一个object,notification center就持有这个object的弱引用以便notification到来时给它发送消息.此时如果这个object被销毁了,你就要在notification center注销以防止notification center给不存在的object发送消息.