博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Object-C内存管理---引用计数

Posted on 2012-04-05 20:55  三块石头  阅读(2066)  评论(0)    收藏  举报

  人懒了,总是会给自己找些借口,其实这些文章早应该写完的.我就不找借口了,接着写.

  当我们讨论Object-C中的NSObject对象时,我们知道alloc方法就是用来申请内存的,release方法是用来随后释放内存的。不幸的是,事情并非总是想像的那么简单。一个运行中的程序可能会引用多个对象,一个对象也可能位于某个数组内或者被某个其他地方的对象所引用.所以,你不能随便释放一个对象的内存,除非你能确保这个对象不再被引用了。

  幸运的是,Foundation框架提供了一个优雅的解决方法来跟踪对象的引用次数,它引入了一个非常直观的机制:引用计数。这个概念可以这样描述:当一个对象被创建时,它的引用次数被设置为1。当你确定需要对一个对象进行保留,你可以调用retain方法来增加它的引用计数,例如:

[myFraction retain];

  Foundation框架的一些内置方法会自动增加对象的引用计数,比如将一个对象增加到数组中。当你不再需要用一个对象时,可以通过调用对象的方法来对对象的引用计数减一,例如:

[myFraction release];

  当一个对象的引用计数为0时,系统知道这个对象将不再需要(因为,从理论上来讲,它不会再被引用),将会收回(dealloc)这个对象的内存,即调用对象的dealloc方法。

  正如你所看到的,这种操作策略需要程序员在合适的时候去增加或者减少对象的引用计数。系统为我们处理一些情况,但是并不是所有的情况。下面我们会用一个例子来讲述引用计数的细节。通过调用对象的retainCount方法,我们可以得到对象被引用的次数。在真实的应用中,你可能并不需要调用这个方法,但是为了描述的清晰,我们用它来展现引用计数的细节。值得注意的时,retainCount这个方法返回一个NSUInteger对象,也就是无符号整数类型。

// 引用计数介绍
#import <Foundation/Foundation.h>

int main (int argc, char *argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  NSNumber *myInt = [NSNumber alloc] initWithInt:100];
  NSNumber *myInt2;
  NSMutableArray *myArr = [NSMutableArray array];
  NSLog (@"myInt retain count = %lx", (unsigned long) [myInt retainCount]);
  [myArr addObject: myInt];
  NSLog (@"after adding to array = %lx",(unsigned long) [myInt retainCount]);
  myInt2= myInt;
  NSLog (@"after asssignment to myInt2= %lx",(unsigned long) [myInt retainCount]);
  [myInt retain];
  NSLog (@"myInt after retain = %lx",(unsigned long) [myInt retainCount]); NSLog (@"myInt2 after retain = %lx",(unsigned long) [myInt2 retainCount]);
  [myInt release];
  NSLog (@"after release = %lx",(unsigned long) [myInt retainCount]);
  [myArr removeObjectAtIndex: 0];
  NSLog (@"after removal from array = %lx" ,(unsigned long) [myInt retainCount]);
  [myInt release];
  [pool drain];
  return 0;
}


程序输出为:

myInt retain count = 1
after adding to array = 2
after asssignment to myInt2 = 2
myInt after retain = 3
myInt2 after retain = 3
after release = 2
after removal from array = 1

  

  NSNumber对象myInt的初始值被设置为100,从输出可以看到它的初始引用计数为1,随后通过调用数组的addObject方法,它被加入到数组myArr中,此时它的引用计数变为2,这个是addObject方法自动做的.如果你查看addObject方法的相关文档,你会发现往任意集合(collection)里面增加或者插入对象,都会增加对象的引用计数。这意味着,如果你随后释放对象(也就是调用release方法),在数组中的对象依然有一个合法的引用,也就是说,对象不会被销毁(deallocated).

   接下来,myInt2的值赋值为myInt。注意赋值操作不会增加对象的引用计数,但这可能存在一些潜在的问题。例如:如果myInt的引用计数被减到0,它所占用的内存将被释放,此时myInt2将会是一个无效的引用。因为,把myInt赋值给myInt2,myInt2并未复制myInt对象,仅仅将将指针指向myInt而已,也就是说,它们指向的相同的内存空间

   接下来,myInt调用retain方法,它的引用计数变为3。第一次引用是对象自己,第二次引用是通过数组,第三次是赋值操作。虽然将对象放入数组会导致数组的引用计数增一,但是赋值操作并不会,所以你必须自己来进行管理。从输出可以看出,myInt2的引用计数也为3,那是因为它们引用了内存中相同的对象。
  

  假设你在程序中用完了myInt并不再需要它,你可以调用release方法来告诉系统。正如你所知,myInt的引用计数将由3变为2。但由于它的引用计数不是0,对这个对象的其他引用(数组myArr和myInt2)依然是有效的.只有当myInt的引用计数变为0时,系统才会释放它的内存空间。

  如果你通过调用数组myArr的removeObjectAtIndex方法来移除它的第一个元素,你将会看到myInt的引用计数下降到1.一般来说,从任意集合里移除一个对象,都会对该对象的引用计数减一。也就是说,下面的代码就会导致问题:

myInt = [myArr ObjectAtIndex: 0]; ...
[myArr removeObjectAtIndex: 0] ...

 

  在这种情况下,调用removeObjectAtIndex后,myInt引用的对象已经无效了,因为这个对象的引用计数已经下降到0了!为了解决这个问题,当我们从数组中获得对象后,我们需要调用一下对象的retain方法,这样就不用担心其他地方对这个对象的引用操作了。

  正如上文所提到的,大部分增加或者插入方法都会对对象执行retain操作,因为这样可以防止不小心在其他地方释放了对象.当你retain一个对象后,你就可以保证在其他地方的操作不会影响到这个对象.

  在IOS应用中,诸如视图(views,用来放图形,图片等容器,通常用在IOS设备的显示屏上)这样的对象常被作为子试图(subviews)加入到已经存在的视图中(在IOS应用开发中你会学到这些知识)。一个典型的代码如下所示:

[myView addSubview: anotherView]; 
[anotherView release];

  因为addSubView方法会retain它的参数anotherView,所以就不用再担心anotherView的释放问题。从另一方面来讲,你也可以随后移除你之前增加的子视图:

[anotherView removeFromSuperview];

  与集合对象一样,当view被移除后,它将会被释放,随后程序中对anotherView的任何引用都会导致程序挂掉。接下来我们在看看字符串引用计数的样例.