内存管理实践

尽管内存管理规则是简单的,但是我们可以通过一些实用的步骤来使内存管理更加稳健、高效

使用访问器方法来简化内存管理

假如类有一个对象属性时,我们需要确保赋值给属性的对象不会被deallocted,因此需要持有该对象而且需要释放当前属性对象;我们应该使用访问器方法来避免在代码中大量使用releaseretain方法

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

//get访问器方法 只需要直接返回该对象,无需retain或者release
- (NSNumber *)count {
    return _count;
}

//set访问器方法,需要先持有newCount 释放oldCount的持有权
- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}

因为在OC中是可以给nil发送消息的,因此即使newCount为空也是有效的
必须先retain后release,因为避免两个count为同一个对象,导致对象不经意间被释放

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

假如想要重置属性值可以使用以下两种方法

//因为使用alloc 所以需要release
- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    [zero release];
}

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

以上两种方法都使用了set访问器方法

应该尽量避免下面的写法,虽然其在简单情况下也会正常,但是这样做很容易导致出错

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

假如你使用KVO,这样写是不符合KVO标准的

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

唯一不应该使用访问器方法设置实例变量的地方就是在初始化方法和dealloc方法中

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

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

因为Counter Class有一个实例变量对象,所以必须完成dealloc方法来释放所有的实例变量对象

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

使用弱引用避免循环引用

当retain一个对象时就对该对象创建一个强引用。一个对象在它的所有的强引用被释放前不会被deallocated。因此,假如两个对象互相引用(或者引用链最后一个对象又引用第一个对象)就有可能造成循环引用

解决循环引用就是使用弱引用,弱引用中源对象并不持有引用对象;因此按照Cocoa惯例:"父对象"强引用他的"子对象",而"子对象"弱引用"父对象"

常用的弱引用: table data sources、视图、notication observers、delegate等

使用弱引用需要注意,避免向已经deallocated的对象发送消息,否则会引起崩溃;因此弱引用对象使用时一般知道另外对象对其的弱引用,并在对象deallocated时通知两外一个对象。例如,向notification center注册接收对象时,通知中心会保留对该对象的弱引用,来在通知时给其发送通知消息。而在deallocated后,向notification center注销该对象,避免再次向其发送消息; 同样的,当取消分配的delegate时,需要setDelegate:nil,这些消息常在deallco方法中发送。

避免正在使用的对象内存销毁

我们在使用内存规则时保证对象在使用时是有效的,但是注意在以下两种情况可能导致出现异常情况:

  1. 当一个对象从集合中移除

    heisenObject = [array objectAtIndex:n];
    [array removeObjectAtIndex:n];
    //heisenObject 此时可能已经无效了

    当对象从集合汇总移除时,会发送一个release消息,此时如果集合是该对象唯一持有者,该对象就会被销毁

  2. 当"父对象"被销毁时

    id parent = <#create a parent object#>;
    // ...
    heisenObject = [parent child] ;
    [parent release]; // Or, for example: self.parent = nil;
    // heisenObject could now be invalid.

    当使用子对象时,其父对象被释放导致被销毁,那么子对象也会被释放,而如果其父对象是其唯一的持有者,那么子对象也会被同时销毁

为了避免出现以上两种情况,应该在收到该对象时retain,而在不再使用时release

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

不要在dealloc中管理稀缺系统资源

不应该在dealloc中管理稀缺资源,例如文件描述符、网络连接以及缓冲区和缓存。更不应该在你认为dealloc将调用是调用dealloc方法,因为dealloc的调用可能会因为bug或者应用终止而导致延迟或者不调用

相反,可以设计一个类,在不再需要这些稀缺系统资源时,进行“清理”。

在dealloc中进行资源管理可能会导致以下问题:

  1. 视图拆除的顺序的依赖性
    对象视图拆除机制本质上是无序的。虽然我们经常会获得期待的拆除顺序,但是这会给项目带来脆弱性。例如如果对象因为意外autorelease而不是release,拆除顺序就有可能改变

  2. 稀缺资源的未回收
    内存管理时,内存泄漏是应该修复的错误,但是通常不会立刻致命。但是如果是该释放的资源没有立即释放就有可能遇到更严重的问题。例如,用完了文件描述符导致应用无法再存储数据

  3. 清理逻辑在错误的线程中执行

    如果一个对象意外的autorelease,它将会在释放时碰巧遇到的任何线程的自动释放池中释放。而这对于那些只能在某个线程中释放的资源,是很致命的

集合持有它所包含的元素对象

将对象添加到集合中,集合就会持有该对象,即强引用该对象。而当集合中移除对象或者该集合本身被释放,集合将release该对象。因此,例如,假如你想要创建一组数组集合,可以这么做:

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

这样写,因为不是alloc调用的的,因此不必release。而我们也必要retain数字对象,因为数组会对其进行retain

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循环中,因为是allocNumber产生的对象因此需要release进行平衡

使用引用计数来进行持有策略

引用计数,通常在retain后称为保留计数,每个对象都有一个保留计数

  • 创建对象时,保留计数为1
  • 向对象发送保留消息,其保留计数加1
  • 向对象发送释放消息,保留计数减1
  • 向对象发送自动释放消息,保留计数在当前自动释放池末尾,保留计数减1
  • 当保留计数为0,则将其deallocated即销毁对象释放内存