retainCount 不会为 0

Objective-C 是通过引用计数的方式进行内存管理的,关于这种机制常听到这样一种说法: “当一个对象的的引用计数值减少到0时,它就会被 dealloc ”,这种说法看似有理,其实却是错的,因为一个对象的引用计数值永远不会为0!下面将从几个方面解释其原因:

获取一个对象的引用计数的方式一般是通过 -retainCount 方法,那么这个方法可能返回0吗?答案是否定的,因为如果引用计数为零时对象就会被销毁,那么对其发送 -retainCount 消息就等同于访问一块被释放的内存,这种行为是未定义的,所以一个正常对象的 -retainCount 方法不会返回 0。(对 nil 对象发送 -retainCount 方法返回 0 情况,其实是 objc_msgSend() 中对为 nil 的 target 做了短路处理,该方法实际并没有被执行)

再从实现的角度看,一个引用计数为1的对象如果收到 -release 消息就应该被释放了,此时不应再对引用计数进行减1的操作,因为这种这个操作根本就是多余的,是在浪费 CPU 资源。

下面是GNUStep中NSObject的实现方式(经过简化):

- (oneway void) release
{
  if (NSDecrementExtraRefCountWasZero(self)) {
      [self dealloc];
  }
}

- (id) retain
{
  NSIncrementExtraRefCount(self);
  return self;
}

- (NSUInteger) retainCount
{
  return NSExtraRefCount(self) + 1;
}

从上面的代码你会发现 -retainCount 的返回值其实是1加上一个叫 NSExtraRefCount 的函数的返回值。那么,这个额外的值是什么呢?原来,当初处于节省内存的目的,NSObject 的引用计数值不是保存在对象中,而是保存在一个全局的表里面,这个表里面保存的就是所谓的 ExtraRefCount 了。但为什么这样能节省内存呢?一种优化的方式是:因为在 Objective-C 程序中往往会使用很多临时对象(autorelease 对象),这些对象的引用计数为 1,所以对于这些对象可以不用在表里面标记,并让 NSExtraRefCount 对查找不到的对象返回0,这样就可以省去一些内存空间了。

最后说明一点,-retainCount 不应该作为调试的手段使用,很多原因都会导致其返回值不可靠!