iphone(object-c) 内存管理(3) 有效的内存管理 前半部分
现在搞iphone开发,一直不是很懂object-c的内存管理机制,看到apple的官方文档写的不错而又没有找到翻译的文章。于是自己在学习它的过程中就顺便把它翻译了,自己的英语不是太好,文字组织能力那就更菜了,读的蹩脚之处还望大家指出,我好在以后的翻译过程中好好改正。第一次翻译,欢迎拍砖,可不要把我拍死了呀!!!
文章中带有LPSTUDY的字样表明是我个人的理解,可能会有不对的地方,敬请指教。
实用内存管理
虽然在内存管理策略一节中的很多基本的概念都很明确了,但是你仍然可以采用一些很实用的步骤来更容易的管理内存,而且可以使你的程序更可靠和健壮,同时还可以减少它的资源需求。
使用存取方法使内存管理更容易
假设你的类有一个对象属性,你就必须确保当你在使用它的时候,它不能被释放掉。你也必须在它被赋值的时候声明所有权,同时确保释放了所有权。
很多时候这是一件枯燥无味的事情,但是如果你一致的使用存取方法,那么你遇到内存管理的问题的可能性就会大大降低了。如果你在代码中对实例变量使用了retain和release方法,那么几乎可以确定,你在做一件错误的事情。
现在你想设置一个Counter对象的count属性,代码如下:
[cpp]
@interface Counter : NSObject {
NSNumber *_count;
}
@property (nonatomic, retain) NSNumber *count;
@end;
property属性声明了两个存取方法。通常情况下,你应该要求编译器去合成(synthesize)此方法,但是,通过看看它们的实现代码会对你有所帮助。在“get”方法中,你仅仅需要返回实例变量,因此没有必要retain或者release
[cpp]
- (NSNumber *)count {
return _count;
}
在“set”方法中,如果其他的人也遵循相同的规则,那么 新的count变量可能会在某一个时刻被释放掉。为了保留此对象,获取它的所有权,你就必须调用retain方法。同时你也必须向它发送release消息,释放它拥有的旧的对象的所有权(object-c中允许对nil发送消息)。你必须先调用[newCount retain] 以防你是在进行自己对自己赋值。如果先释放的话,retain操作就没有意义了。
[cpp]
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
使用存取方法来设定属性值
例如你向实现一个重置counter对象的方法。你有很多选择,第一种实现方式是用alloc创建NSNumber实例,然后释放它。
[cpp]
- (void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[self setCount:zero];
[zero release];
}
第二种方法是使用一种更方便的构造方法来创建NSNumber对象。于是就没有必要retain或者release消息了。
[cpp]
- (void)reset {
NSNumber *zero = [NSNumber numberWithInteger:0];
[self setCount:zero];
}
请注意上面的两种方式都使用了“set”方法
下面的例子对于简单的情况也可以正常工作。但是正如它的目的是去避免存取方法一样,这样做在一些情况下总会导致错误(例如,忘记了retain或者release,或者如果实例变量的内存管理语义改变了)
[cpp]
- (void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[_count release];
_count = zero;
}
Notealso that if you are using key-value observing, then changing the variable inthis way is not KVO compliant.(我现在还不懂kvo,不知道什么叫kvo)
不要在init和dealloc方法中使用存取方法
唯一你不能通过使用存取方法来设定变量的地方是在inializer方法和dealloc方法。为了初始一个数字对象为0,你可以在init方法中这样实现:
[cpp]
- init {
self = [super init];
if (self) {
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
为了能够用count去初始化一个counter对象,你可以实现一个initWithCount方法:
[cpp]
- initWithCount:(NSNumber *)startingCount {
self = [super init];
if (self) {
_count = [startingCount copy];
}
return self;
}
LPSTUDY: 这是初始化方法,因此不用先release。我自己也不是很懂它为什么要调用copy方法,而不是调用retain方法。Copy相当于自己再申请一块拥有相同数据的内存,而retain相当于获取了原来数据的所有权。如果你知道,请留一下言,谢谢。
使用弱引用来避免retain循环
retain 一个对象会对这个对象创建一个强引用。直到一个对象所有的强引用被release了,此对象所占有的内存才会被释放。于是有一种问题,被称作“retaincycle”产生了。
LPSTUDY:
这个很容易理解,就特别像操作系统中的死锁,a引用b,b引用c,c再引用a,然后形成了一个环。或者咱们再简单一点,a中有b,b中有a。现在我想释放a,那么我必须先释放b,而我要释放b,我就必须先释放a,这样就死锁勒,谁也释放不了,不知道这样说是不是会容易理解一点。如果想仔细弄清楚引用循环的话,这个估计需要大家去仔细搜索一下相关的技术文档了。
在图标1中展示了一个潜在的引用循环。Document类对于它的每一个Page,都有一个Page对象。每一个Page类中也有一个属性来指明其所在的Document。如果Document对象拥有指向Page的强引用而且Page类也拥有一个指向Document类的强引用,那样的话就没有对象可以被释放了。Document的引用计数直到所有的Page对象释放完了才可以为0,自己才能释放。对于Page类,同样的,需要所有的Document对象释放后太才可以释放,于是就死锁了。
图1:循环引用的示例图
retain cycle的解决办法是使用弱引用,弱引用指的是是一个非拥有关系,也就是说拥有一个对象的引用并没有拥有retain这个对象。
为了保持对象图之间的联系,强引用是必要的。因此,Cocoa采用的一种约定:“parent”对象应该拥有对其“children”的强引用,“children”应该拥有对其“parents”的弱引用。
因此,对于图标1来说,Document类拥有对其Page的强引用,Page拥有对Document类的弱引用。
Cocoa中弱引用的例子存在于但是并不局限于像table的数据源,大纲视图条目,通知类观察者,以及多种多样的目标和委托中。
当你想发送消息到一个弱引用的对象时,你必须非常非常小心。如果此对象已经被释放了,程序会崩溃。因此,你必须清楚的知道这个对象什么时候是有效的。在大多数情况下,被弱引用的对象能够感知所有对其进行弱引用的对象,当此对象被释放的时候,它需要负责向引用它的对象发送通知。例如,当你向通知中心注册了一个对象,通知中心存储了对这个对象的弱引用,而且在合适的通知发出的时候向这个对象发送消息。如果这个对象被释放,你需要向通知中心取消注册来阻止向这个对象发送消息。同样的,如果一个委托对象被释放了,你需要通过向其他的对象发送setDelegate:nil来删除所有的委托链接。这样的方法通常是在委托对象的dealloc方法中做。
LPSTUDY:
个人理解(我还没有自己做过委托类,oc刚学,感觉这个流程最符合事实上的逻辑,就这样写了。如果哪个地方不对,希望大家看的时候能够指出来,我再改正一下,希望不会因为我的错而误认子弟):a想委托b去干活,在a中可以调用setDelegate:b这样的方法来设置b为a的委托,那么这样a就可以委托b了,a把自己要干的活都扔给b,由b去完成。a要想让b干活,b必须首先是活着的,因为a并不知道b什么时候死掉了,于是b必须在它死掉之前告诉a自己死掉了,不要再委托我了。b告诉自己挂掉的消息是b在自己的dealloc中调用a的setDelegate:nil方法来实现的。
不要释放你正在使用的对象
cocoa的对象拥有策略能够确保你在调用的方法中的整个生存期中接收到的对象都是有效的。同样的,你也可以返回这个接收到的对象(不是很通顺,不知道received objects具体该怎么翻译)。这种情况对于你是返回实例变量还是运算结果并不重要。重要的是当你在使用它的时候,对象必须是有效的。
有一些例外的场合,下面是其中的两个。
1. 当一个对象从基础收集类collection classes中删除掉的时候
[cpp]
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.
当一个对象从基础收集类中remove的时候,它是发送了一个release方法,而不是autorelease方法。如果此收集类是这个对象仅有的拥有者,那么被remove的对象(像本例中heisenObject)就会立刻被释放掉。
2. 当父对象被释放了
[cpp]
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.
为了阻止这样的情况发生,你需要retain一下这个对象。当你结束使用它的时候,release它。例如:一些场合中你从另一个对象中获取了一个对象,然后release了父对象。如果在release父对象触发了父对象的dealloc方法,而且此父对象是孩子的唯一拥有者,那么这个孩子(本例中如heisenObject)会被释放掉(父对象的dealloc方法中是向子类发送release消息)
[cpp]
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];
作者:lipeng08
相关新闻>>
- 发表评论
-
- 最新评论 更多>>