coding 的演示功能不让用,原来搭建的博客访问不了了。索性将全部博客迁移到简书,这篇是旧文章,欢迎大家以后来简书看我的博客
玩iOS已经一年多了,经常用到单例,但也仅仅是停留在会用的层次,至于为什么这么用,内部怎么实现的,从未研究过。直到上次去腾讯面试,被问到了单例的一些细节,才发现自己对单例的了解还很浅薄。这几天仔细研究了一下单例,也总结了一些心得。如果有写的不好的地方,希望大家指点。
单例介绍
1.什么是单例
说到单例首先要提到单例模式,因为单例模式是单例存在的目的
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
单例,顾名思义:单独的实例。
简单的说,单例是一个特殊的实例,在单例所属的类
中只存在单例这么一个实例,并且单例类似全局变量,在系统任意地方都能访问单例
2.单例用处
根据单例模式的定义,我们知道一般两种情况下使用单例:
- 系统中某种对象只能存在一个,多了就会出问题
- 系统中某种对象实例只需要一个就够用了,多了占内存
对于第一种情况,我们必须使用单例,对于第二种情况,我们虽然可以不用单例,但是单例是更优的选择
iOS的系统中有很多地方用的都是单例
[UIApplication sharedApplication];
[NSNotificationCenter defaultCenter];
[NSFileManager defaultManager];
[NSUserDefaults standardUserDefaults];
[NSURLCache sharedURLCache];
[NSHTTPCookieStorage sharedHTTPCookieStorage];
iOS单例的创建
1.单线程单例
我们知道对于单例类
,我们必须留出一个接口来返回生成的单例,由于一个类中只能有一个实例,所以我们在第一次访问这个实例的时候创建,之后访问直接取已经创建好的实例
@implementation Singleton
+ (instancetype)shareInstance
{
static Singleton* single;
if (!single) {
single = [[Singleton alloc] init];
}
return single;
}
@end
ps:严格意义上来说,我们还需要将
alloc
方法封住,因为严格的单例是不允许再创建其他实例的,而alloc
方法可以在外部任意生成实例。但是考虑到alloc
属于NSObject,iOS中无法将alloc
变成私有方法,最多只能覆盖alloc
让其返回空,不过这样做也可能会让使用接口的人误解,造成其他问题。所以我们一般情况下对alloc
不做特殊处理。系统的单例也未对alloc
做任何处理
2.@synchronized单例
对于一个实例,我们一般并不能保证他一定会在单线程模式下使用,所以我们得适配多线程情况。在多线程情况下,上面的单例创建方式可能会出现问题。如果两个线程同时调用shareInstance
,可能会创建出2个single来。所以对于多线程情况下,我们需要使用@synchronized
来加锁。
@implementation Singleton
+ (instancetype)shareInstance
{
static Singleton* single;
@synchronized(self){
if (!single) {
single = [[Singleton alloc] init];
}
}
return single;
}
@end
这样的话,当多个线程同时调用shareInstance
时,由于@synchronized
已经加锁,所以只能有一个线程进入创建single
。这样就解决了多线程下调用单例的问题
3.dispatch_once单例
使用@synchronized
虽然解决了多线程的问题,但是并不完美。因为只有在single
未创建时,我们加锁才是有必要的。如果single
已经创建.这时候锁不仅没有好处,而且还会影响到程序执行的性能(多个线程执行@synchronized
中的代码时,只有一个线程执行,其他线程需要等待)。那么有没有方法既可以解决问题,又不影响性能呢?
这个方法就是GCD中的dispatch_once
@implementation Singleton
+ (instancetype)shareInstance
{
static Singleton* single;
static dispatch_once_t onceToken; //①onceToken = 0;
dispatch_once(&onceToken, ^{
NSLog(@"%ld",onceToken); //②onceToken = 140734731430192
single = [[Singleton alloc] init];
});
NSLog(@"%ld",onceToken); //③onceToken = -1;
return single;
}
@end
dispatch_once
为什么能做到既解决同步多线程问题又不影响性能呢?
下面我们来看看dispatch_once
的原理:
dispatch_once
主要是根据onceToken
的值来决定怎么去执行代码。
- 当
onceToken
= 0时,线程执行dispatch_once
的block
中代码 - 当
onceToken
= -1时,线程跳过dispatch_once
的block
中代码不执行 - 当
onceToken
为其他值时,线程被线程被阻塞,等待onceToken
值改变
当线程首先调用shareInstance
,某一线程要执行block
中的代码时,首先需要改变onceToken
的值,再去执行block中的代码。这里onceToken
的值变为了140734731430192。
这样当其他线程再获取onceToken
的值时,值已经变为140734731430192。其他线程被阻塞。
当block
线程执行完block
之后。onceToken
变为-1。其他线程不再阻塞,跳过block
。
下次再调用shareInstance
时,block已经为-1。直接跳过block
。
这样dispatch_once
在首次调用时同步阻塞线程,生成单例之后,不再阻塞线程。dispatch_once
是创建单例的最优方案
总结:
- 单例模式是一个很好的设计模式,他就像一个全局变量一样,可以让我们在任何地方都使用同一个实例。
- 如果要自己创建单例模式,最好使用
dispatch_once
方法,这样即可解决多线程问题,又能达到高效的目的
单例虽然好用,不过他并不适合继承和扩展,所以使用单例的时候要注意这点。千万不要任何东西都使用单例,要适可而止