Cocoa和Objective-C选择一种主动的、策略驱动的例程来保持对象,并在不再需要的时候进行清理。这种例程和策略建立在引用计数的基础上。每个Cocoa对象都带有一个整数,用于指示对其持久性感兴趣的其它对象的数目。这个整数被称为对象的保持数(“保持” 是为了避免和“引用”重复)。
当通过alloc或者allocWithZone:类方法创建对象的时候,Cocoa做了一些非常重要的工作:
§ 它将对象的isa指针—NSObject类中唯一的公共实例变量—指向对象的类,因此将对象集成到类层次的运行时视图中。
§ 它将对象的保持数—一个隐藏的实例变量,所有对象都有—设置为1。
为对象分配内存之后,通常需要将其实例变量设置为合理的初始值,以便进行初始化(NSObject声明了init方法作为这个目的的原型)。
当释放一个对象—也就是向对象发送一个release消息时—NSObject会减少它的保持数。如果保持数从1下降到0,对象就会被解除分配。对象的解除分配有两个步骤:首先是对象的dealloc方法被调用,以释放实例变量和动态分配的内存;然后是操作系统将对象的本身销毁,并回收对象占用的内存。如果不希望对象很快消失,在创建对象之后向它发送一个retain消息,对象的保持数就会增加到2。这样,就需要两个release消息才能导致对象的释放。
重要提示:永远不应该直接调用对象的dealloc方法。
如果客户对象在程序的作用域之外希望保持接收到的对象,则可以保持该对象—也就是向它发送一个retain消息。保持一个对象会增加该对象的保持数,从而表示希望拥有该对象。客户对象有责任在一段时间后释放该对象。如果对象的创建者将该对象释放,但同时有一个客户对象已经保持了该对象,则该对象会一直持续到客户对象将它释放为止。可以不保持对象,而是通过发送copy或copyWithZone:消息来对其进行拷贝(很多子类—如果不是大多数的话—都封装了某种数据采纳方法,或遵循这种协议)。拷贝一个对象不仅仅是对其进行复制,而且几乎总是将它的保持数设置为1。根据对象的本质和可能的用法,拷贝可以是浅拷贝,也可以是深拷贝。深拷贝将对象复制为被拷贝对象的一个实例变量,而浅拷贝只是复制那些实例对象的引用。
Cocoa引入了一种延迟对象释放的机制,称为自动释放(autoreleasing)机制。自动释放机制通过自动释放池(由NSAutoreleasePool类定义)来实现。自动释放池是位于显式定义的作用域内的一个对象集合,该作用域被标志为最后释放。自动释放池可以嵌套。当您向一个对象发送一个autorelease消息时,Cocoa就会将该对象的一个引用放入到最新的自动释放池。它仍然是个正当的对象,因此自动释放池定义的作用域内的其它对象可以向它发送消息。当程序执行到作用域结束的位置时,自动释放池就会被释放,池中的所有对象也就被释放
到目前为止,对象生命周期的讨论主要关注整个周期中的对象管理机制。但是,指导如何使用这些机制的是对象的所有权策略。这个策略可以概括如下:
§ 如果通过分配和初始化(比如[[MyClass alloc] init])的方式来创建对象,就拥有这个对象,需要负责该对象的释放。这个规则在使 用NSObject的便利方法new时也同样适用。
§ 如果拷贝一个对象,也拥有拷贝得到的对象,需要负责该对象的释放。
§ 如果保持一个对象,就部分拥有这个对象,需要在不再使用时释放该对象。
反过来,
§ 如果从其它对象那里接收到一个对象,则不拥有该对象,也不应该释放它(这个规则有少数的例外,在参考文档中有显式的说明)。
和其它规则一样,这些策略也有一些例外和经常出错的地方:
§ 如果您通过类工厂方法来创建对象(比如NSMutableArray arrayWithCapacity:方法),则可以假定您接收到的对象已经自动被放到自动释放池了。您不应该自行将它释放,如果您需要保持该对象,则应该保持(retain)它。
§ 为了避免循环引用,子对象不能保持它的父对象(父对象是该子对象的创建者,或者将该子对象作为实例变量持有的对象)。
对象的生命周期中可能发生的另一个基本事件是归档。归档是将组成一个面向对象程序中的相关对象形成的网状结构—对象图—转化为一种可持久的形式(通常是一个文件),该形式可以保存对象图中对象的标识和彼此之间的关系。在解档时,可以通过这个档案重新构造出程序的对象图。为了参与归档(和解档),对象必须支持通过NSCoder类定义的方法对实例变量进行编码(和解码)。为了这个目的,NSObject采纳了NSCoding协议。