【设计模式】五、单例模式(独一无二的对象)
一、概述:
有一些对象我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表对象、日志对象、充当打印机、显卡等设备的驱动程序的对象。事实上这些对象只需要一个实例,如果制造出多个实例就会导致很多问题发生。(利用静态类变量、静态方法和适当的访问修饰符的确也可以做到只存在一个实例。)
苏格拉底诱导式回答:(参考《Head First 设计模式》)
如何创建一个对象? | new MyObject() |
万一另外一个对象想创Myobject会怎样?可以再次new MyObject吗? | 是的,当然可以。 |
所以一旦有一个类,我们是否都能多次的实例化它? | 如果是公开的类 就可以 |
如果不是的话,会怎样? |
如果不是公开的类,只有同一个包内的类可以实例化它,但是仍可以实例化它很多次 |
可以这么做吗? public Myclass { private Myclass(){} }
|
我没想过,但是这是合法的定义,有一定的道理。 |
怎么说呢? | 我认为含有私有的构造器类不能呗实例化。 |
又可以使用私有的构造器对象吗? | 恩,我想Myclass内的代码是唯一能调用此构造器的代码,但是这又不太合乎常理。 |
WHY? | 因为只有Myclass的实例才能调用Myclass的构造器,但是因为没有其他类能够实例化Myclass,所以我们得不到这样的实例。 |
嘿,我有个想法。 你认为如何? public Myclass { public static MyClass getInstance(){} } MyClass有一个静态方法,我们可以这样调用这个方法: MyClas.getInstance(); |
|
为何调用的时候用MyCLass类名,而不是对象名。 | 因为getInstance是类方法,是一个静态方法,你需要使用类名。 |
有意思,假如把这些合在一起“是否”就可以初始化一个MyClass? public MyClass { private MyClass(){} public static Myclass getInstance() { return new MyClass(); } }
|
当然可以 |
好了,你能想出第二种实例化对象的方式吗? | MyClass.getInstance(); |
你能够完成代码是MyClass只有一个实例被产生吗? | 恩,大概可以吧。。。。 |
单例模式优点
- 正如前面说的,单例模式在内存中只有一个实例,减少了内存开支。特别是一个对象需要频繁的创建、销毁时,而创建与销毁的性能有无法优化,单例模式的优势就非常明显。
- 单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 单例模式可以避免对资源的多重占用。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问。
单例模式缺点
- 单例模式一般没有接口,扩展很困难,除了修改代码基本上没有第二种途径实现。
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的。
- 单例模式与单一职责原则有冲突。
二、在IOS中的应用
单例模式在iOS
开发中的使用还是蛮多的,许多Foundation
、Cocoa
和UIKit
中的类都实现了单例模式,比如应用程序本身UIApplication
、文件操作类NSFileManager
、消息中心NSNotificitonCenter
等系统都已经给我们实现单例,我们只需要使用就好了。在iOS
中使用单例模式要使用类方法,通过类方法返回该类的唯一对象。
在ios中的应用主要有以下三种方式
1、
static Singleton *instance = nil; + (Singleton *)sharedInstance { if (instance == nil) { instance = [[super allocWithZone:NULL] init]; } return instance; } + (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return NSUIntegerMax; //denotes an object that cannot be released } - (void)release { //do nothing } - (id)autorelease { return self; }
可以看到这种方式,使用静态成员维持了一个永久存在的对象,而且覆盖了alloc
方法(alloc
方法会调用allocWithZone:
方法),并且也覆盖了所有与引用技术有关的方法,这都使这个对象不会被销毁。这样看上去基本实现了我们需要的,但是写起来麻烦不说,还有很大的一个问题,那就是多线程问题,如果是在多线程中那么该种方法就不能保证只产生一个对象了。所以这种方式只是介绍一下,并不推荐使用。
2、引入头文件
程序员都是偷懒的,现在流行使用一个宏定义来搞定这许多的事,而且考虑的更加周全。
单例包含以下接口
+ (MyClass*) sharedInstance;
+ (void) purgeSharedInstance;
调用sharedInstance会创建并返回单例
调用purgeSharedInstance会销毁单例
手动调用alloc也可以保证是单例,你可以这样调用
[[MyClass alloc] initWithParam:firstParam secondParam:secondParam];
只是要保证在sharedInstance之前调用,因为只有一次创建机会。
下面是使用宏的写法“
MyClass.h: ======================================== #import "SynthesizeSingleton.h" @interface MyClass: SomeSuperclass { ... } SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(MyClass); @end ======================================== MyClass.m: ======================================== #import "MyClass.h" @implementation MyClass SYNTHESIZE_SINGLETON_FOR_CLASS(MyClass); ... @end ========================================
3、
iOS
在4.0以后推出了block
和GCD
,这两个特性给iOS
开发带来的很大的便利,也使开发变得更加趣味话。那么如何通过GCD
+block
来实现单例模式呢,这主要归功于dispatch_once(dispatch_once_t *predicate, ^(void)block)
这个GCD
的函数,他有两个参数第一参数是一个指向dispatch_once_t
类型结构体的指针,用来测试block
是否执行完成,该指针所指向的结构体必须是全局的或者静态的,第二个参数是一个返回值与参数均为空的block
,在block
体中进行对象的初始化即可。dispatch_once
在程序的生命周期中保证只会被调用一次,所以在多线程中也不会有问题。 该种方法使用方法:
+ (Singleton *)sharedInstance { static Singleton *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[Singleton alloc]init]; }); return instance; }
dispatch_once的作用就是执行且在整个程序的声明周期中,仅执行一次某一个block对象。简直就是为单例而生的嘛。而且,有些我们需要在程序开头初始化的动作,如果为了保证其,仅执行一次,也可以放到这个dispatch_once来执行。
然后我们看到它需要一个断言来确定这个代码块是否执行,这个断言的指针要保存起来,相对于第一种方法而言,还需要多保存一个指针。
方法简介中就说的很清楚了:对于在应用中创建一个初始化一个全局的数据对象(单例模式),这个函数很有用。
如果同时在多线程中调用它,这个函数将等待同步等待,直至该block调用结束。
这个断言的指针必须要全局化的保存,或者放在静态区内。使用存放在自动分配区域或者动态区域的断言,dispatch_once执行的结果是不可预知的。
总结:1.这个方法可以在创建单例或者某些初始化动作时使用,以保证其唯一性。2.该方法是线程安全的,所以请放心大胆的在子线程中使用。(前提是你的dispatch_once_t onceToken
对象必须是全局或者静态对象。这一点很重要,如果不能保证这一点,也就不能保证该方法只会被执行一次。)
参考博客:
水滴石穿 Keeping faith. --- wtlucky's Blog