1 单例模式
1.1 什么是单例
单例模式在整个工程中,相当于一个全局变量,就是不论在哪里需要用到这个类的实例变量,都可以通过单例方法来取得,而且一旦你创建了一个单例类,不论你在多少个界面中初始化调用了这个单例方法取得对象,它们所有的对象都是指向的同一块内存的存储空间(即单例类保证了该类的实例对象是唯一存在的一个)。
1.2 单例模式的优缺点
-
优点
- 一个类只被实例化一次,提供了对唯一实例的受控访问。
- 节省系统资源。
- 允许可变数目的实例。
-
缺点
- 一个类只有一个对象,可能造成责任过重,在一定程度上违背了“单一职责原则”。
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
1.3 单例的实现
单例的实现分为两种:懒汉式和饿汉式。
- 懒汉式:顾名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。
- 饿汉式:饿了肯定会饥不择食,所以在单例类加载的时候就进行实例化。
特点和选择:
- 由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能,这是以空间换时间。
- 在访问量较小时,采用懒汉实现,这是以时间换空间。
1.3.1 懒汉式
- 使用
@synchronized
static id manager = nil;
+ (instancetype)shareInstance {
// 防止多次加锁
if (!manager) {
@synchronized (self) {
if (!manager) {
manager = [[super allocWithZone:NULL] init];
}
}
}
return manager;
}
第一次if(!manager)判断是为了避免在对象创建后多次访问导致的多次加锁,浪费性能。第二次if(!manager)判断就是判断此时单例是否存在,不存在就重新创建。
- 使用GCD
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[super allocWithZone:NULL] init];
});
return manager;
}
dispatch_once它没有使用重量级的同步机制,性能也优于前者,并且更加高效。
dispatch_once
无论使用多线程还是单线程,都只执行一次,在安全的前提下也保证了性能。
dispatch_once
主要是根据onceToken
的值来决定怎么执行代码:
- 当
onceToken
为0时,线程执行dispatch_once
的block
中的代码。 - 当
onceToken
为-1时,线程跳过dispatch_once
的block
中的代码。 - 当
onceToken
为其他值时,线程被阻塞,等待onceToken
值改变。
dispatch_once
的执行流程:
- 当线程调用
shareInstance
,此时onceToken
为0,执行dispatch_once
的block
中的代码,此时onceToken
中的值为其他值。 - 这时如果有其他线程再调用
shareInstance
方法时,onceToken
值为其他值,线程阻塞。 - 当
block
线程执行完block
后,onceToken
变为-1。其他线程不再阻塞,跳过block
。 - 下次再调用
shareInstance
时,onceToken
为-1,直接跳过block
。
1.3.2 饿汉式
当类被加载的时候就创建,因为一个类在整个生命周期中只会被加载一次,所以它肯定只有一个线程对其进行访问,此时再创建他就是线程安全的,就不需要使用线程锁来保证其不会被多次创建。
static id manager = nil;
+ (void)load {
[super load];
manager = [[super allocWithZone:NULL] init];
}
+ (instancetype)shareInstance {
return manager;
}
- (instancetype)copyWithZone:(NSZone *)zone {
return manager;
}
- (instancetype)mutableCopyWithZone:(NSZone *)zone {
return manager;
}
+ (instancetype)allocWithZone: