内存管理 reference count

来源:互联网 发布:强制卸载软件工具 编辑:程序博客网 时间:2024/05/22 14:12
reference count  (引用计数)的概念其实很简单, 就是每个对象都有一个reference count 用来表明有多少其他的对象目前正保留一个对它的引用(reference).  对象A 想要引用对象B, A 就把B 的 reference count 加 1。 当A 结束了对B 的引用, A 就把 B 的reference count 减 1.   当没有任何对象再引用 B 时, B 的 reference count
就减为0, B 就被清除(deallocated), 内存就被释放。清除B的时候, 被B所用的对象的 reference count 也可能减小, 也可能使它们被清除。

reference count  (引用计数)是 NSObject 和 NSAutoreleasePool 两个类提供的机制。引用计数不是自动的, 编译器无法决定一个对象的生命周期,所以必须手动调用以下方法来控制 引用计数.

  •  NSObject -retain reference count 增 1。  NSObject -release reference count 减一。  NSAutoreleasePool -autorelease 在将来的某时reference count 减一。  NSObject -alloc 为一个对象分配内存,并返回这个对象,reference count 为1。
     NSObject -copy 生成一个对象的拷贝,并返回此对象, reference count 为 1。









发送+alloc 或者 +allocWithZone: 消息给类, 就会预留出内存给一个新的实例, reference count 为 1。 通过 retain ,  其他对象可以引用这个类实例。
[object retain];
同样

[object release];

减少reference count至零时,对象立即被清除, 此时,为了防止错误的发送消息给一个不存在的对象, 我们最好重新设置对象的引用, 通常使用 nil:

object = nil;

获取reference count 的值, 需要发送 retainCount 消息给对象。 这对调试程序有用。

  • release 规则:

总之,

以下每个方法必须有一个 -release 对应:

+alloc
+allocWithZone
-copy
-mutableCopy
-copyWithZone
-mutableCopyWithZone
-retain


  • 特殊情况
有时, reference count 不这磨简单。 当需要返回一个对象的引用时, 下面这个方法是错误的:

- (NSObject *) incorrectMethod1
{
NSObject *result = [[NSObject alloc] init];
return result;
}

错误是:

没有相对应 alloc 消息的 -release . 记住 :新的NSObject 实例生成时,reference count 为1, 并且实例被返回,但如果这个实例返回后不被使用, 那末就出现了内存泄露。


另一个错误的方法:
-(NSObject *) incorrectMethod2
{
    NSObject *result = [[ NSObject alloc] init];
    [result release];
    return result;
}

错误:

虽然参照规则, 使用了- release 去对应 +alloc , 但是发送了 -release 消息之后, result 就被清除了,而程序倚赖返回的result reference , 因此程序就可能崩溃了, 因为返回的reference 指向了一个早已被清除了的对象。


那末如何解决这个问题呢,既不会发生内存泄露或者返回一个无效的对象reference?答案就是使用 release pool   (释放池) 。 一个临时存放对象的地方, 当一个对象被加入释放池中, 这个对象就注册了 - 稍候一定会接收到一个 -release  的消息。当另一个对象想要保留一个被调用对象的reference,  就会发送一个- retain 消息给那个要使用的对象。在未来某个时候,释放池会被清除,同时池内所有的对象被释放。池内任何对象的reference count 为零时, 这些对象就被清除。


来看两个情况:


情况1。

step 动作 retain count 1 对象被分配内存, (allocated) 1 2 对象被加入释放池 1 3 对象由方法返回 1 4 对象被其他调用者 retain 住。
2 5 释放池被清除(deallocated), 对象被释放。 1      
  由于对象的retain count 是1,所以调用对象必须最终释放被条用的对象,否则会发生内存泄露。


情况2:

    retain count 1 对象被分配内存, 1 2 对象被加入释放池 1 3 对象由方法返回 1 4 对象没有被调用者 retain 住。 1 5 释放池被清除, 对象被清除。 0


调用者并未retain 由方法返回的对象。结果, 释放池被清除时,对象也被清除了,所以调用者无须再释放对象了。


可以看到先前我们的问题得以解决, 对象的释放是发生在对象被方法返回之后,

  • NSAutorleasePool 类

Cocoa 的NSAutoreleasePool 类实现 释放池。 -autorelease 方法实现对象在释放池的添加和释放。

[object autorelease]; //将对象放入释放池,意味着稍后会被释放。


来看这个正确的方法:

-(NSObject *) correctMethod
{
    NSObject *result = [[NSObject alloc] init];
    [result autoreleqase];
    return result;
}


allocated 初始化之后,reference 被赋值给 result. reference count =1。 当 result 被 autorelease 时,对象被放入释放池。 reference count 仍为1。

如果调用-correctMethod的代码没有retain 返回的对象 (NSObject) ,那末对象的 reference count 将为零, 对象被清除, 释放池被清除。 如果调用代码retain 住返回对象, 对象的reference count 将暂时为2。 当释放池最终被清除时,对象的reference count 减少到1。但对象并未被清除。 调用代码所keep 住的reference 依然有效,。


  • release pool 是谁生成的?
Cocoa 程序使用App Kit 框架,在内部事件循环(event loop) 开始的时候自动生成release pool.  当程序代码执行结束, 控制返回到应用程序对象时(通常在事件循环的最后),应用程序对象发送 release 消息给 释放池。

当然,特殊情况下,比如要加强程序性能和减低程序的内存要求,或者,比如制作一个命令行程序,不会用到 App Kit 程序框架时,就需要自己生成释放池。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

在事件循环最后,释放这个池:
[pool release];

  • release or autorelease ?
-release 方法要远比 -autorelease 效率高。 所以, 除非要使用 -autorelease 的特殊功能, 否则,尽量使用 - release. 过多使用 -autorelease 将导致程序运行缓慢。

  • 新规则:
  1. 当alloc, copy, retain 一个对象后, 日后你必须负责释放对象, 用- release 或者 -autorelease 。如果你没有 alloc, copy, 或者 retain 过一个对象, 那就没有必要去 release 它。
  2. alloc 或者 copy 之外的方法,当返回一个对象给你时,这个对象通常只在这个方法内有效,而且在方法的最后可以安全返回。你如果需要继续使用这个对象 (比如把对象存入一个实例变量), 就必须要磨 retain 要磨 copy 这个返回对象。
  3. 如果不再需要reference 一个对象,就应该使用 -autorelease 而不是 release. 同时,为了程序性能考虑, 尽量使用 -release 而不是 -autorelease.
原创粉丝点击