一、简介
单例在我们开发中是最常用的设计模式,在iOS中也是如此。单例可以保证某个类的实例在程序中是唯一的,便于进行资源和数据的共享。使用的设计原则是单一职责原则。
我们来看看在iOS中本身自带的类或者方法哪些使用了单例的模式:
(1)UIAccelerometer类和sharedAccelerometer方法,一般如果方法名中有shared这样的词,就可以认为这是一个可以整个应用程序共享的实例变量,一般是使用了单例。
(2)UIApplication类和sharedApplication方法,我们一般使用该方法来创建全局变量。
(3)NSBundle类和mainBundle方法。
(4)NSFileManager类和defaultManager方法。
(5)NSNotificationCenter类和defaultManager方法。其中NSNotificationCenter也实现了观察者模式。
(6)NSUserDefaults类和defaultUser方法。
(7)NSURLCache(请求缓存)等。
1.1 单例的使用
接下来我们将使用单例(Singleton)写一个简单的页面内loading加载动效。对单例的使用进行说明。具体的效果如下图:(GIF速度太快,将就看下。具体实现可在结尾下载Demo)
页面内加载.gifa. 创建单例
方法1: 比较传统的写法:
// 方法1 比较传统的写法
+ (id)sharedInstance
{
static id sharedInstance = nil;
@synchronized(sharedInstance){
if(sharedInstance == nil)
{
sharedInstance = [[self alloc] init];
}
}
return sharedInstance;
}
方法1的概述:懒汉式。典型的时间换空间
synchronized的使用时为了线程安全,比如有两个线程A和B,加入他们同时调用shared方法,如果不加同步的话则很可能会导致并发,这样可能就创建出来了两个实例,而不是真正的单例模式了,所以需要加上同步(synchronized),来保证同一个时刻只有一个线程在访问这个实例。这样做是实现了单例,而且是线程安全的,但这样就没有一点问题了吗?问题还是有的,那就是时间的问题,因为这样写之后我们每次调用shared都会去判断singleInstance == nil是不是成立的,这样就避免不了浪费了判断的时间,有人或许会说就那么一丁点的时间还用考虑吗?是的,我觉得能节省的时间我们一点都不要浪费。如果你也认可,那么我们接着往下看其他方法。
方法2: 双重检查加锁:(推荐)
// 方法2:双重检查加锁 推荐使用
+ (id)sharedInstance {
volatile static SingleLoading *singleInstance = nil;
if (singleInstance == nil) {
@synchronized(self) {
if (singleInstance == nil) {
singleInstance = [[self alloc] init];
}
}
}
return singleInstance;
}
方法2的概述:减少了方法1同步判断耗费的时间,也能保证线程安全。
所谓双重检查机制指的是每次进入这个方法时先去判断实例是不是为nil,如果不为nil则直接返回,反之则进入同步检查,然后再判断是不是为nil,若不存在则创建一个实例,这样就只需要同步一次就行了,从而减少了同步时的判断需要耗费的时间,代码也更为清晰。而使用volatile修饰,是因为被volatile修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
方法3: GCD的dispatch_once方法:(推荐)
// 方法3: GCD的dispatch_once
+ (id)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
方法3概述,使用GCD的dispatch_once方法创建单例,也是目前使用中最常见的。方法很多时候本没有好坏之分,学会多分析每种使用可以带来的效益才是最重要的。通过上面的方法,对单例的创建我们有了一定的认识。
二、单例的滥用
概述:
单例给我们带来方便的同时也有一定的副作用,因为单例对象一旦创建,对象指针是保存在静态区的,单例对象在堆中分配的内存空间只有在程序终止后才会释放,过多的单例必定会增大我们消耗的内存,所以只有当我们确实需要唯一的使用对象时才需要考虑单例模式,切勿滥用单例,引用开头的话:单例应该只用来保存全局的状态,并且不能和任何作用域绑定。如果这些状态的作用域比一个完整的应用程序的生命周期要短,那么这个状态就不应该使用单例来管理。所以我们在使用单例的时候一定要想清楚了,我们是不是真的有必要用单例,如果这个对象的创建不是那么的费时费力,或者这个对象没必要再应用的整个生命周期中一直存在,那么我们是不是考虑一下换用其他的方式,而非单例?