单例模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类 必须保证只有一个实例存在。 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用sharedInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
苹果官方示例中如下定义单例
static MyGizmoClass *sharedGizmoManager = nil;
+ (MyGizmoClass*)sharedManager
{
if (sharedGizmoManager == nil) {
sharedGizmoManager = [[super allocWithZone:NULL] init];
}
return sharedGizmoManager;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedManager] retain];
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain
{
return self;
}
- (NSUInteger)retainCount
{
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release
{
//do nothing
}
- (id)autorelease
{
return self;
}
使用allocWithZone是因为保证分配对象的唯一性
原因是单例类只有一个唯一的实例,而平时我们在初始化一个对象的时候, [[Class alloc] init],其实是做了两件事。 alloc 给对象分配内存空间,init是对对象的初始化,包括设置成员变量初值这些工作。而给对象分配空间,除了alloc方法之外,还有另一个方法: allocWithZone.
而实践证明,使用alloc方法初始化一个类的实例的时候,默认是调用了 allocWithZone 的方法。于是覆盖allocWithZone方法的原因已经很明显了:为了保持单例类实例的唯一性,需要覆盖所有会生成新的实例的方法,如果有人初始化这个单例类的时候不走[[Class alloc] init] ,而是直接 allocWithZone, 所以我们应该怎么使用单例呢?
+ (instancetype)shareInstance
{
static LLSingleton *shareInstace = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareInstace = [[super allocWithZone:nil] init];
});
return shareInstace;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
return [LLSingleton shareInstance];
}
- (id)copyWithZone:(NSZone *)zone
{
return [LLSingleton shareInstance];
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
return [LLSingleton shareInstance];
}
我们之所以使用dispatch_once 主要是因为为了加锁保证单例的唯一性
2019-07-15 14:09:22.051730+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = 0
2019-07-15 14:09:22.051841+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = 768
2019-07-15 14:09:22.051919+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = -1
2019-07-15 14:09:22.051986+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = -1
2019-07-15 14:09:22.052056+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = -1
通过输出我们可以发现,在 dispatch_once 执行前,onceToken 的值是 0,因为 dispatch_once_t 是由 typedef long dispatch_once_t 而来,所以在 onceToken 还没被手动赋值的情况下,0 是编译器给 onceToken 的初始化赋值。
在 dispatch_once 执行过程中,onceToken 是一个很大的数字,这个值是 dispath_once 内部实现中一个局部变量的地址,并不是一个固定的值。
当 dispatch_once 执行完毕,onceToken 的值被赋为 -1。之后再次调用的时候,onceToken已经是-1了,就直接跳过dispatch_once的执行
dispatch_once 第一次执行,block 被调用,调用结束需标记 onceToken。
dispatch_once 是同步阻塞线程,第一次执行过程中,有其它线程的请求需要等待 dispatch_once 的第一次执行结束才能被处理。
dispatch_once 同步线程执行完毕onceToken会修改值为-1,有其它线程执行该 dispatch_once,则直接跳过 block 执行后续任务。
上面是通过覆盖其他初始化方法来保证分配对象的唯一性
当然我们也可以直接禁止直接禁止掉其他的初始化方法
+ (instancetype) alloc __attribute__((unavailable("call sharedInstance instead")));
+ (instancetype) new __attribute__((unavailable("call sharedInstance instead")));
- (instancetype) copy __attribute__((unavailable("call sharedInstance instead")));
- (instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));
这样如果外部调用单例的其他初始化方法就会直接报错
为了避免创建单例的时候写太多重复代码,我们可以通过以下两种宏定义来创建单例
一、直接覆盖其他初始化方法
// .h文件
#define SingletonH(name) + (instancetype)shared##name;
// .m文件
#define SingletonM(name) \
static id _instace; \
\
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super allocWithZone:zone]; \
}); \
return _instace; \
} \
\
+ (instancetype)shared##name \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [[self alloc] init]; \
}); \
return _instace; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instace; \
}
二、禁止掉其他初始化方法
// .h文件
#define SINGLETON_H(_type_) + (_type_ *)sharedInstance;\
+(instancetype) alloc __attribute__((unavailable("call sharedInstance instead")));\
+(instancetype) new __attribute__((unavailable("call sharedInstance instead")));\
-(instancetype) copy __attribute__((unavailable("call sharedInstance instead")));\
-(instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));\
// .m文件
#define SINGLETON_M(_type_) + (_type_ *)sharedInstance{\
static _type_ *_instace = nil;\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instace = [[super alloc] init];\
});\
return _instace;\
}