Objective-C 内存管理之思考方式

引用计数概念解释

Objective-C中的内存管理,也就是引用计数。可以用开关房间的灯为例来说明引用计数的机制。

假如办公室里的照明设备只有一个。上班进入办公室的人需要照明,所以要把灯打开。而对于下班离开办公室的人来说,已经不需要照明了,所以要把灯关掉。

解决这一问题的办法是使办公室在还有至少1人的情况下保持开灯状态,而在无人时保持关灯状态。

  1. 最早进入办公室的人开灯。
  2. 之后进入办公室的人,需要照明。
  3. 下班离开办公室的人,不需要照明。
  4. 最后离开办公室的人关灯(此时已无人需要照明)。

为判断是否还有人在办公室里,这里导入计数功能来计算“需要照明的人数”。

  • 第一个进入办公室,需要照明的人数+1。计数值从0变成了1,因此要开灯。
  • 之后每当有人进入办公室,需要照明的人数就+1。
  • 每当有人下班离开办公室,需要照明的人数就-1.
  • 最后一个人下班离开办公室是,需要照明的人数就-1,当计数值从1变成了0,因此要关灯。

这样就能在不需要照明的时候保持关灯状态。办公室中仅有的照明设别得到了很好的管理。

在Objective-C中,“对象”相当于办公室的照明设备。在显示世界中办公室的照明设备只有一个,但在OCzhong ,可以同时处理好多对象。

对照明设备所做的动作 对OC对象所做的动作 OC对应的方法
开灯 生成对象 alloc/new/copy/mutableCopy等方法
需要照明 持有对象 retain方法
不需要照明 释放对象 release方法
关灯 废弃对象 dealloc方法

内存管理的思考方式

正确的思考方式是:

  • 自己生成对象,自己所持有。
  • 非自己生成的对象,自己也能持有。
  • 不再需要自己持有的对象时释放。
  • 非自己持有的对象无法释放。

Cocoa框架中Foundation框架类库的NSObject类负担内存管理的指责。OC内存中的alloc/retain/release/dealloc方法分别只带NSObject类的alloc类方法,retain实例方法,release实例方法和dealloc实例方法。

自己生成的对象,自己持有

使用以下名称开头的方法名意味这自己生成的对象只有自己持有:

  • alloc
  • new
  • copy
  • mutableCopy

注:自己指 对象的只用环境

例如:

/*
 * 自己生成并持有对象
 */
    
id obj = [[NSObject alloc] init];

// id obj = [NSObject new];
    
/*
 * 自己持有对象
 */
复制代码

使用NSObject类的alloc类方法就能自己生成并持有对象。只想生成并持有对象的指针被赋给变量obj。另外new类方法也可以。

copy方法利用基于NSCopying方法约定,由各类实现的copyWithZone:生成并持有对象的副本。与copy方法类似,mutableCopy方法利用给予NSMutableCopying方法约定,由各类实现的mutableCopyWithZone:方法生成并持有对象的副本。两者的区别在于,copy方法生成不可变更的对象,而mutableCopy方法生成可变更的变量。

非自己生成的对象,自己也能持有

用上述项目之外的方法缺的对象,因为非自己生成并持有,所有自己不是该对象的的持有者。例如NSArray类的array类方法。

/*
 * 取得非自己生成并持有的对象
 */
    
id obj = [NSMutableArray array] ;
    
/*
 * 对象已存在,但自己不持有对象
 */
 
[obj retain];

/*
 * 自己持有对象
 */
 
复制代码

代码中,NSMutableArray对象被赋给变量obj,但obj自己并不持有该对象。使用retain方法可以持有对象。

不再需要自己持有的对象时释放

自己持有的对象,一旦不再需要时,持有这有义务释放该对象。释放使用release方法。

/*
 * 自己生成并持有的对象
 */
    
id obj = [NSObject new] ;
    
/*
 * 自己持有对象
 */
 
[obj release];

/*
 * 自己释放对象
 * 只想对象的指针仍然被保留在变量obj中,貌似能够访问,但对象一经释放绝对不可访问
 */
 
复制代码

用alloc/new方法由自己生成并持有的对象就通过release方法释放了。自己生成而非自己所持有的对象,若用retain方法变为自己持用,也同样用release方法释放。

如果要用某个自定义方法生成对象,并将其返回给该方法的调用方,那么代码如下:


- (id) allocObject {
    /*
     * 自己生成并持有对象
     */
    
    id obj = [[NSObject alloc] init];
    
    /*
     * 自己持有对象
     */
    
    return obj;
}
 
复制代码

如上所示,原封不动第返回用alloc方法生成并持有的对象,就能让调用方法也持有该对象。请注意该方法的命名规则,是以alloc开头命名的,那么自定义方法中,以alloc/new/copy/mutableCopy命名的方法,也意味自己生成并持有对象。

/*
 * 取得非自己生成并持有对象
 */
    
id obj1 = [obj0 allocObject];
    
/*
 * 自己持有对象
 */
复制代码

类似于[NSMutableArray array]的自定义方法如下(命名不能以alloc/new/copy/mutableCopy开头)

- (id) Object {
    
    id obj = [[NSObject alloc] init];
    
    /*
     * 自己持有对象
     */
    
    [obj autorelease];
    
    /*
     * 取得对象的存在,但自己不持有对象
     */
    
    return obj;
}

复制代码
    id obj1 = [obj0 Object];
    
    /*
     * 取得的对象存在,但不自己持有
     */
     
    [obj1 retain];
    
    /*
     * 自己持有对象
     */

复制代码

上述方法中,使用了autorelease方法。用该方法,可以使取得的对象存在,但不自己持有。autorelease方法提供使对象在超出的生存范围时能够自动并正确的释放。

  • release为调用之后立即释放
  • autorelease为调用之后注册到autoreleasepool中,pool结束时自动调用release,aoturelease不是立即释放,而存在一定时长

无法释放非自己持有的对象

对于用alloc/new/copy/mutableCopy方法生成并持有的对象,或是用retain方法持有的对象,由于持有者是自己,所以在不需要该对象的时候需要将其释放。而由此意外所得到的对象绝对不能释放。如果强行释放,则会造成crash。

/*
 * 自己生成并持有对象
 */
   
id obj = [NSObject new];
    
/*
 * 自己持有对象
 */
    
[obj release];
    
/*
 * 对象已释放
 */
    
    
[obj release];
   
/*
 *对象释放后再次释放已非自己持有的对象,导致crash
 */
复制代码
id obj1 = [obj0 Object];
    
/*
 * 取得的对象存在,但不自己持有
 */
    
[obj1 release];
    
/*
 * 释放了非自己持有的对象,导致crash
 */
复制代码

以上,就是“引用计数式内存管理”的思考方式。