神刀安全网

高级内存管理编程指南(Advanced Memory Management Programming Guide)(二):内存管理实践

内存管理实践

虽然在Memory Management Policy中描述的基本概念很简单,有一些实际的步骤,你可以使内存管理更容易,并帮助确保您的程序可靠性和健壮性的同时减少资源需求。

使用访问器方法使内存管理更容易

如果你的类有一个属性是一个对象,你必须确保,当你使用它,被设置为值的任何对象都不释放。 因此,当它被设置时,必须声明对象的所有权。你还必须保证对这些对象所有权的放弃。

有时候似乎很麻烦,如果你坚持用get和set这种方法方法来实现,那么内存管理的问题出现几率就大幅度减少了。如果对于整个代码的实例变量,使用的是retain和release,你几乎可以肯定是在做错误的事情。

想象一个你想为它设值的计数器变量。

@interface Counter : NSObject @property (nonatomic, retain) NSNumber *count; @end;

属性声明了两个访问器方法。通常情况下,你应该要求编译器来synthesize这些方法;但是,看一下它们可能被实现的形式是有帮助的。

在get访问器,就是返回synthesized实例变量,所以没有必要retain或release:

- (NSNumber *)count {     return _count; }

set方法中,如果每个人都遵守相同的规则,你必须承担起新的计数可在任何时间进行设置,所以你必须通过发送一个retain的消息确保它不会被销毁,来维持住对象的所有权。此外,还必须通过发送一个release消息放弃老的计数对象在这里的所有权。(在Objective-C发送消息nil是允许的,所以如果实现,因此就算_count 还没有旧值,也不会出错。)你必须在[newCount retain]之后再(对旧值)发送release,因为你不想因为意外而造成dealloc。

- (void)setCount:(NSNumber *)newCount {     [newCount retain];     [_count release];     // Make the new assignment.     _count = newCount; }

使用访问器方法来设置属性值

假设你想实现复位计数器的方法。你有几个选择。第一种做法就是用alloc来新建一个NSNumber实例,然后再对应一个release。

- (void)reset {     NSNumber *zero = [[NSNumber alloc] initWithInteger:0];     [self setCount:zero];     [zero release]; }

第二个使用快速构造器来创建一个新NSNumber对象。 因此,不需要retain或release消息。

- (void)reset {     NSNumber *zero = [NSNumber numberWithInteger:0];     [self setCount:zero]; }

需要注意的是两者都使用set访问方法。

下面的做法,对于简单的情况而言,肯定是没问题的。但是,因为它的实现绕开了set方法, 那么在特定情况下会导致错误(例如,比如当你忘记了retain或者release,或者如果实例变量的内存管理发生了变化)。

- (void)reset {     NSNumber *zero = [[NSNumber alloc] initWithInteger:0];     [_count release];     _count = zero; }

还需要注意的是,如果你使用key-value observing,那么这种对于值的复位就跟KVO不兼容了。

不要在初始化方法和dealloc中使用访问器方法

你不应该使用存取方法来设置实例变量的唯一地方是在初始化方法和dealloc。为了初始化一个counter,并将值设置为0,你可以实现一个初始化方法如下:

- init {     self = [super init];     if (self) {         _count = [[NSNumber alloc] initWithInteger:0];     }     return self; }

为了让counter的初始化值为非0值,你可以实现一个名为initWithCount:的方法:

- initWithCount:(NSNumber *)startingCount {     self = [super init];     if (self) {         _count = [startingCount copy];     }     return self; }

由于计数器类有一个对象的实例变量,还必须实现一个dealloc方法。它应该通过发送一个release消息放弃任何实例变量的所有权,最终也应该调用父类的实现:

- (void)dealloc {     [_count release];     [super dealloc]; }

使用弱引用来避免循环引用

Retain一个对象,实际是对一个对象的强引用。一个对象在所有的强引用都解除之前,是不能被dealloc的。这导致一个被称为“环形持有”的问题:两个对象相互强引用 (可能是直接引用,也可能是通过其他对象间接地引用。)

下图所示的对象关系就构成了一个循环引用。Document对象持有多个Page对象。每个Page对象又具有一个Document引用来指示它归属的文档。如果Document对象对Page对象有一个强引用,而Page对象对Document也有一个强引用,则两者都不能被dealloc。全部Page对象都release之前,Document对象的引用数永远不会为0,而如果Document对象存在,Page对象也无法被release。

高级内存管理编程指南(Advanced Memory Management Programming Guide)(二):内存管理实践
图1 循环引用示意图

循环引用问题的解决方案是使用弱引用。弱引用是一个非持有关系,已经被引用的对象不对它的拥有者进行持有。

为了实现上面的对象图,肯定是需要强引用的(如果只有弱引用,那么Page和Paragraph就没有了持有者,造成它们会被dealloc)。因此Cocoa建立了一个约定,父对象应该维持对于其子对象的强引用,并且子对象应该只对父对象建立弱引用。

所以,图1中document对象对page有一个强引用(retains),但是page
对象对 ocument对象有一个弱引用(不是retain)。

在Cocoa中包含了弱引用的例子,但不限于,表中的数据源,大纲视图项, notification观察者,以及其他的target以及delegate。

你必须小心将消息发送到你持有只是一个弱引用的对象。当你发送消息给一个被dealloc的弱引用对象时,你的应用程序会崩溃。因此,你必须细致地判断对象是否有效。多数情况下,被弱引用的对象是知道其他对象对它的弱引用的(比如循环引用的情形),所以需要通知其他对象它自己的dealloc。举例, 当你向Notification Center注册一个对象时,Notification Center对这个对象是弱引用的,并且在有消息需要通知到这个对象时,就发送消息给这个对象。当这个对象dealloc的时候,你必须向Notification Center取消这个对象的注册,这样,这个Notification Center就不会再发送消息给这个不存在的对象了。同样,当一个delegate对象被dealloc的时候,必须向其他对象发送一个setDelegate:消息,并传递nil参数,从而将代理的关系撤销。这些消息通常在对象的dealloc方法中发出。

避免你正在使用的对象被dealloc

Cocoa的所有权策略规定,收到的对象通常应该在整个调用方法的范围仍然有效。这也应该是在当前方法内部,不必担心你收到的返回对象会被dealloc。对象的getter方法返回一个被缓存的实例或者一个计算出来的值,这并不重要。重要的是这个对象在你使用它的时候会一直有效。

偶尔有例外的情况,主要分为两类:

  • 1.当一个对象从collection classes中删除的时候。
heisenObject = [array objectAtIndex:n]; [array removeObjectAtIndex:n]; // heisenObject could now be invalid.

当一个对象从基本集合类之一删除,它发送一个release(而不是autorelease)消息。如果集合是被删除对象的唯一拥有者,被移除的对象是立即被释放。

  • 2.当一个“父对象”被释放。
id parent = <#create a parent object#>; // ... heisenObject = [parent child] ; [parent release]; // Or, for example: self.parent = nil; // heisenObject could now be invalid.

在某些情况下检索来自另一个对象的对象,然后直接或间接地释放父对象。如果释放父对象导致它被释放,并且父对象是子对象的唯一所有者,那么子对象(例子中的heisenObject)将在同一时间被释放(假设在父类中的dealloc方法中,给子对象发送的是release消息,而不是autolease消息)。

为了防止这种情况下,你可以在接收到heisenObject 的时候retain一次,并且当你用完的时候 ,release。例如:

heisenObject = [[array objectAtIndex:n] retain]; [array removeObjectAtIndex:n]; // Use heisenObject... [heisenObject release];

不要使用dealloc来管理关键系统资源

通常,你不应该在dealloc中来管理稀缺系统资源,比如文件描述符、网络连接、缓存等。尤其注意,你不应该这样设计类:你想让系统什么时候调用dealloc,系统就什么时候调用。dealloc的调用可能会被推迟或者搁置,比如因为bug或者系统性能下降。

相反,如果你有一个类,管理了稀缺资源,它就必须知道它什么时候不再需要这些资源,并在此时立即释放资源。通常情况下,此时,你会调用release来dealloc,但是因为此前你已经释放了资源,这里就不会遇到任何问题。

如果你尝试把资源管理问题的职能交给dealloc,可能会导致很多问题。比如:

  • 1.对象图的拆除顺序问题
    对象图拆卸机制本质上是无序的。尽管你可能通常希望和获得一个特定的顺序。例如,如果一个对象被意外地autorelease,而不是release,拆卸顺序可能改变,这可能会导致意想不到的结果。

  • 2.系统稀缺资源不能回收
    内存泄漏是应该被修复的bugs,但它们一般都不会是立即致命的。然而,如果当你希望稀缺资源被释放,但没有释放,你可能会遇到更严重的问题。例如,如果你的应用程序运行了文件描述符,用户可能无法保存数据。

  • 3.释放资源的操作被错误的线程执行
    如果对象是在一个意想不到的时间自动释放,它将被线程池中的线程来dealloc。对于只能从一个线程操作的资源来说,这很容易造成致命的后果。

Collections拥有他们所包含的对象

当你添加一个对象到一个collection,例如(数组,字典,集合),collection会取得该对象的所有权。当对象从集合中删除或当集合本身释放时,集合将放弃所有权。因此,如果你想创建数字数组,可以像下面这样做:

NSMutableArray *array = <#Get a mutable array#>; NSUInteger i; // ... for (i = 0; i < 10; i++) {     NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];     [array addObject:convenienceNumber]; }

在这种情况下,你没有调用alloc ,所以没有必要调用release。没有必要保留新numbers(convenienceNumber),因为数组会这么做。

NSMutableArray *array = <#Get a mutable array#>; NSUInteger i; // ... for (i = 0; i < 10; i++) {     NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];     [array addObject:allocedNumber];     [allocedNumber release]; }

这种做法,我们在for循环内部向allocedNumber发送了与alloc相对应的release消息。因为Array的addObject:方法实际上对这个对象做了retain处理,那么这个对象(allocedNumber)不会因此而被dealloc。

要理解这一点,把自己放在那些实现集合类的人的位置。你要确保加入的对象只要继续存在于Collection里,就不应该被dealloc,因此你在添加这个对象时,向它发送了retain消息。删除这个对象时,向它发送了release消息。当你这个collection类自己dealloc时,对容器内所有的对象发release。

通过引用计数实现所有权策略

所有权政策是通过引用计数实现的,通常retain方法后被称为“retain count”后。每个对象都有一个引用计数。

  • 当你创建一个对象,它有一个引用计数1。
  • 当你给对象发送一个retain。引用计数+1。
  • 当你给对象发送一个release消息,它的引用计数-1。
    当你给对象发送一个autorelease消息。它的引用计数在当前自动释放池块结束后 -1。
  • 如果对象的引用计数减少到 0,它被释放。

重要:其实你应该没有理由想知道一个对象的retain count。这个数值有时候会造成对你的误导:你不知道实际上有些系统框架的对象会对你关注的那个对象进行retain。在调试内存问题的时候,你只需要遵守所有权规则就行了。

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 高级内存管理编程指南(Advanced Memory Management Programming Guide)(二):内存管理实践

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址