单例模式用于当一个类只能有一个实例的时候, 通常情况下这个“单例”代表的是某一个物理设备比如打印机,或是某种不可以有多个实例同时存在的虚拟资源或是系统属性比如一个程序的某个引擎或是数据。用单例模式加以控制是非常有必要的。
单例模式需要达到的目的
1. 封装一个共享的资源
2. 提供一个固定的实例创建方法
3. 提供一个标准的实例访问接口
单例模式的创建
本文以创建一个MySingletonClass的单例模式为例。首先,我们需要定义一个类MySingletonClass.
- @interface MySingletonClass:NSObject {
- }
并且为其添加一个类方法(注意,这里不是实例方法)+(id)sharedInstance;一个基本的实现写法如下:
- static MySingletonClass *sharedCLDelegate = nil;
- +(MySingletonClass *)sharedInstance{
- @synchronized(self) {
- if(sharedCLDelegate == nil) {
- [[[self class] alloc] init]; // assignment not done here
- }
- }
- return sharedCLDelegate;
- }
在上面的代码中(用到了关键字@synchronized是为了保证我们的单例的线程级别的安全,可以适用于多线程模式下。)static变量sharedCLDelegate用于存储一个单例的指针,并且强制所有对该变量的访问都必须通过类方法 +(id)sharedInstance,在对 +(id)sharedInstance第一次调用时候完成实例的创建。这里值得留意一下的是,上面代码中用的是[[selfclass] alloc],而不是 [MySingletonClass alloc],一般情况下这两种写法产生同样的效果,但是这里这样做是为了更好的利用OOP的性质,[selfclass]可以动态查找并确定类的类型从而便于实现对该类的子类化。
对实例化的控制
为了完全的实现实例的单态性,必须通过一定手段来避免实例多次被创建。+(id)sharedInstance控制了单例的创建和访问,但是并不能控制其它地方的代码通过alloc方法来创建更多的实例,因此我们还要重载任何一个涉及到allocation的方法,这些方法包括 +new, +alloc,+allocWithZone:, -copyWithZone:, 以及 -mutableCopyWithZone: 另外,+(id)sharedInstance也需要稍作修改。
- + (id)hiddenAlloc
- {
- return [super alloc];
- }
- + (id)alloc
- {
- NSLog(@"%@: use +sharedInstance instead of +alloc", [[self class] name]);
- return nil;
- }
- + (id)new
- {
- return [self alloc];
- }
- +(id)allocWithZone:(NSZone*)zone
- {
- return [self alloc];
- }
- - (id)copyWithZone:(NSZone *)zone
- { // -copy inherited from NSObject calls -copyWithZone:
- NSLog(@"MySingletonClass: attempt to -copy may be a bug.");
- [self retain];
- return self;
- }
- - (id)mutableCopyWithZone:(NSZone *)zone
- {
- // -mutableCopy inherited from NSObject calls -mutableCopyWithZone:
- return [self copyWithZone:zone];
- }
- +(id)sharedInstance修改如下:
- + (MySingletonClass *)sharedInstance {
- @synchronized(self) {
- if (sharedCLDelegate == nil) {
- [[[self class] hiddenAlloc] init]; // assignment not done here
- }
- }
- return sharedCLDelegate;
- }
如果不考虑类的子类化,+hiddenAlloc这个方法可以省略。由于我们是用[selfclass]来实现类型的动态识别,用[[selfclass] hiddenAlloc]可以避免调用到被重载过的alloc方法。此外,hiddenAlloc也为可能的子类化提供了一个调用原始alloc方法的机会。上面重载过的alloc方法只是给出一个log信息并且返回nil。Copying方法里只是简单的增加了retain的计数并没有返回一个新的实例。这也正体现了单例模式的性质,因为技术上来讲,拷贝一个单例是错误的(因为是“单例”)所以在copyWithZone方法中我们给出了一个错误信息,当然也可以扔出一个exception。
单例的销毁
通常我们在 -(void)applicationWillTerminate:(UIApplication *)application方法中调用如下方法:
- + (void)attemptDealloc
- {
- if ([sharedCLDelegate retainCount] != 1)
- return;
- [sharedCLDelegate release];
- myInstance = nil;
- }
值得注意的是,上面这个attemptDealloc方法顾名思义,只是试图释放掉这个单例。如果retain的计数不为1,说明还有其他地方对该单例发送过retain消息。考虑到一个单例模式的生存周期是整个程序结束为止。所以,在程序的任何一个地方都没有必要向这个单例发送retain消息,即便是对这个单例有引用。而是调用sharedInstance方法来引用这个单例,这样做是安全的,也是合乎单例模式的技术含义的。
iOS中的单例模式应用
iOS中好几个类都是采用了单例模式,比如NSApplication, NSFontManager, NSDocumentController,NSHelpManager, NSNull,NSProcessInfo, NSScriptExecutionContext, NSUserDefaults.