之前在 实现Singleton 模式——七种实现方式中发现java 的单例有七种实现方式,对里面的懒汉和饿汉模式稍微研究了下,发现IOS 里面也可以对应实现。
简述
面向对象应用程序中的单例类(singleton class)总是返回自己的同一个实例。它提供了对象所提供的资源的全局访问点。与这类设计相关的设计模式称为单例模式。
大家在开发过程中也见过不少的单例,比如UIApplication、UIAccelerometer(重力加速)、NSUserDefaults、NSNotificationCenter,当然,这些是开发Cocoa Touch框架中的,在Cocoa框架中还有NSFileManager、NSBundle等。
1、懒汉模式:实现原理和懒加载其实很像,如果在程序中不使用这个对象,那么就不会创建,只有在你使用代码创建这个对象,才会创建。这种实现思想或者说是原理都是iOS开发中非常重要的,所以,懒汉式的单例模式也是最为重要的,是开发中最常见的。
2、饿汉模式:在没有使用代码去创建对象之前,这个对象已经加载好了,并且分配了内存空间,当你去使用代码创建的时候,实际上只是将这个原本创建好的对象拿出来而已。
3.使用GCD代替手动锁实现单例模式
4.使用宏封装直接便于开发使用
talk is cheap, show me the code 直接上代码展示
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 我已经在之前的文章中 IOS nonatomic 与atomic 分析 描述过此类问题,有趣的是我在网上看到有朋友问:
+(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修饰符
- 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。具体对于两种方法的描述已经有很多人描述过了,详见: iOS类方法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 其中一种即可实现单例。
3.使用GCD代替手动锁实现单例模式(推荐使用)
这个在所有的使用者中是最多的,我们使用dispatch_once 方法实现单例模式。
代码如下:
static id instance = nil;
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];
});
return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];
});
return instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return instance;
}
为什么推荐使用GCD代替手动锁实现单例模式 :
1.写法简单,比起需要手动加锁简单很多。
2.性能优异: @synchronized采用的是递归互斥锁来实现线程安全,而dispatch_once的内部则使用了很多原子操作来替代锁,以及通过信号量来实现线程同步,而且有很多针对处理器优化的地方。
此处有专门的文章细说GCD的优异:
细说@synchronized和dispatch_once
简单的来说:
就是@synchronized 在多线程中加锁,其他线程是等待的,造成了线程资源浪费。
而 dispatch_once主要是根据onceToken的值来决定怎么去执行代码。
1.当onceToken = 0时,线程执行dispatch_once的block中代码
2.当onceToken = -1时,线程跳过dispatch_once的block中代码不执行
3.当onceToken为其他值时,线程被阻塞,等待onceToken值改变
当线程调用shareInstance,此时onceToken = 0,调用block中的代码,此时onceToken的值变为140734537148864。当其他线程再调用shareInstance方法时,onceToken的值已经是140734537148864了,线程阻塞。当block线程执行完block之后,onceToken变为-1.其他线程不再阻塞,跳过block。下次再调用shareInstance时,block已经为-1.直接跳过block。
4.使用宏封装直接便于开发使用
这边就只是简单的告诉你可以通过宏封装单例达到方便直接使用单例。
// .h文件的代码
#define NTSingletonH(name) + (instancetype)shared##name;
// .m文件中的代码(使用条件编译来区别ARC和MRC)
#if __has_feature(objc_arc)
#define NTSingletonM(name)\
static id instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[super alloc]init];\
});\
return instance;\
}\
+ (instancetype)shared##name\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[self alloc]init];\
});\
return instance;\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return instance;\
}
#else
#define NTSingletonM(name)\
static id instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[super alloc]init];\
});\
return instance;\
}\
+ (instancetype)shared##name\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[self alloc]init];\
});\
return instance;\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return instance;\
}\
- (oneway void)release\
{\
}\
- (instancetype)retain\
{\
return instance;\
}\
- (NSUInteger)retainCount\
{\
return 1;\
}\
- (instancetype)autorelease\
{\
return instance;\
}
#endif
使用方式就是在新类 NewSingleton 中
@interface NewSingleton : NSObject
NTSingletonH(Manager)
@end
@implementation NewSingleton
NTSingletonM(Manager)
@end
需要的时候简单调用 [NewSingleton sharedManager] 即可