一,单线程模式单例
// 单线程单例
+(instancetype)sharedLoadData
{
static Singleton *singleton;
if (!singleton ) {
singleton = [[Singleton alloc] init];
}
return singleton;
}
1.单线程单例只有在单个线程使用的情况下使用,在多线程的情况下,会产生线程不安全的情况,严格意义上来说,我们还需要把alloc方法变为私有方法才行,严格的单例是不允许再创建其他实例的,而alloc方法可以在外部任意生产实例。换句话说,假如在两条线程里调用sharedLoadData方法,可能会产生两个singleton实例,这样单例就失去意义了。
二,多线程加锁单例
// @synchronized加锁
+(instancetype)sharedLoadData
{
static Singleton *singleton;
@synchronized (self) {
if (!singleton) {
singleton = [[Singleton alloc] init];
}
}
return singleton;
}
2.加锁以后,当多个线程同时调用sharelnstanc时,由于@synchronized已经加锁,只能有一个线程创建singleton实例。这样就解决了第一种情况的弊端,但是也有缺点:只有在singletion未创建时,加锁才是必要的,如果singleton已经创建,这个时候还加锁的话,会影响性能。
三,系统GCD创建单例
+(instancetype)sharedLoadData
{
static Singleton *singleton = nil;
static dispatch_once_t onceToken;
// dispatch_once 无论使用多线程还是单线程,都只执行一次
dispatch_once(&onceToken, ^{
singleton = [[Singleton alloc] init];
});
return singleton;
}
3.GCD创建单例不仅可以解决多条线程的线程安全问题,也能保证性能。
dispatch_one主要是根据onceToken的值来决定怎么去执行代码,
1.当onceToken = 0时,线程执行dispath_once的block中代码
2.当onceToken = -1时,线程跳过dispatch_one的block中的代码不执行,
3.当onceToken为其他值时,线程被阻塞,等待onceToken值改变。
当线程调用sharelnstance,此时onceToken =0 ,调用block中的代码,此时onceToken的值变为140734537148864。当其他线程再调用shareinstance方法时,onceToken的值已经是140734537148864,线程阻塞。当block线程执行完block之后,onceToken变为-1,其他线程不再阻塞,跳过block。下次再调用sharelnstance时,block已经为-1,直接跳过blokc.
四,单例实现方式与优缺点
1、懒汉模式:实现原理和懒加载其实很像,如果在程序中不使用这个对象,那么就不会创建,只有在你使用代码创建这个对象,才会创建。这种实现思想或者说是原理都是iOS开发中非常重要的,所以,懒汉式的单例模式也是最为重要的,是开发中最常见的。
2、饿汉模式:在没有使用代码去创建对象之前,这个对象已经加载好了,并且分配了内存空间,当你去使用代码创建的时候,实际上只是将这个原本创建好的对象拿出来而已。
3.使用GCD代替手动锁实现单例模式
4.使用宏封装直接便于开发使用
(1).懒汉模式
static id instance = nil;
// 懒加载 线程不安全 单例
+ (instancetype) ShareInstance
{
if (instance == nil) {
instance = [[self alloc] init];
}
return instance;
}
// 懒加载 加锁 单例
+ (instancetype) ShareInstance1
{
@synchronized (self) { //为了线程安全,加上互斥锁
if (instance == nil) {
instance = [[self alloc] init];
}
}
return instance;
}
1)加synchronized 是为了保证单例的读取线程安全,为什么需要添加synchronized
+(instancetype)sharedSingleton{
static id instance = nil;
if (!instance) {
@synchronized (self) {
instance = [[self alloc] init];
}
}
return instance;
}
肯定不行的,稍微对synchronized 有点了解就知道这种只是“锁”住了对象的创建,没有“锁”住 if 判断。如果两个线程都进到了 if 里面,一样可以生成两个对象。
2)static
修饰局部变量:
修饰了局部变量的话,那么这个局部变量的生命周期就和不加static的全局变量一样了(也就是只有一块内存区域,无论这个方法执行多少次,都不会进行内存的分配),不同的在于作用域仍然没有改变
修饰全局变量:
如果不适用static的全局变量,我们可以在其他的类中使用extern关键字直接获取到这个对象,可想而知,在我们所做的单例模式中,如果在其他类中利用extern拿到了这个对象,进行一个对象销毁,
extern id instance;
instance = nil;
这时候在这句代码之前创建的单例就销毁了,再次创建的对象就不是同一个了,这样就无法保证单例的存在,所以对于全局变量的定义,需要加上static修饰符
3) allocWithZone 与copyWithZone方法
我们在项目中一般是直接调用自己定义的类方法:ShareInstance,但是有时候也会调用alloc方法直接对单例进行初始化,那么也会导致没有产生该单例,所以我们需要保证应用中只有一个该类的对象需要重写它的allocWithZone 方法,alloc调用的底层也是allocWithZone方法,直接与上述的单例方法类同。
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 解决多线程问题
@synchronized(self){
if (instance == nil) {
// 调用super的allocWithZone方法来分配内存空间
instance = [super allocWithZone:zone];
}
}
return instance;
}
如果使用copy创建出新的对象的话,那么就不能够保证单例的存在了也会导致同样的问题。此处直接返回instance就可以了。
- (id)copyWithZone:(NSZone *)zone
{
return instance;
}
(2).饿汉模式
在没有使用代码去创建对象之前,这个对象已经加载好了,并且分配了内存空间,当你去使用代码创建的时候,实际上只是将这个原本创建好的对象拿出来而已。在alloc之前如何将对象直接赋值呢,有两种方式:load和initialize。
load 会在类加载到运行环境中的时候就会调用且仅调用一次,同时注意一个类只会加载一次(类加载有别于引用类,可以这么说,所有类都会在程序启动的时候加载一次,不管有没有在目前显示的视图类中引用到)initialize方法:当第一次使用类的时候加载且仅加载一次
static id instance = nil;
+ (void)load
{
instance = [[self alloc]init];
}
+ (void)initialize
{
instance = [[self alloc]init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
if (instance == nil) {
instance = [super allocWithZone:zone];
}
return instance;
}
+ (instancetype)sharedInstance
{
return instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return instance;
}
实际上只需要实现 load 与 initialize 其中一种即可实现单例。