什么是单例?
通过单例方法获取到的实例,在系统中是唯一的。 为什么说通过单例方法获取到的实例是唯一的呢?这个问题接下在分析。
什么时候适合使用单例?
系统中某个对象只需要存在一个实例(多了就浪费内存)
单例的创建
创建单例,有两种方式(😂我知道的就两种),一种是通过@synchronized
来创建,一种是通过GCD
来创建。这里不考虑单线程下创建单例。
1、@synchronized
方式创建
static UserInfo *manager;
+ (instancetype)sharedManager {
if(!manager) {
@synchronized (self) {
if (!self) {
manager = [[UserInfo alloc] init];
}
}
}
return manager;
}
2、GCD
方式创建
+ (instancetype)shareManager{
static dispatch_once_t onceToken;
static UserInfo *manager;
dispatch_once(&onceToken, ^{
if (manager == nil) {
manager = [[UserInfo alloc] init];
}
});
return manager;
}
两种方式创建分析:
通过@synchronized
方式创建的单例,加锁的目的是为了防止多线程同时去创建单例实例
通过GCD
方式创建的单例,我们申明的静态局部变量static dispatch_once_t onceToken;
的初始值为0(tip:dispatch_once_t
是一个长整形),当获取实例的时候dispatch_once
会判断onceToken
的值,当值为0的时候,才会走dispatch_once
的block方法,创建完成之后会将onceToken
的值赋为非0。下一次获取实例的时候dispatch_once
会判断onceToken
的值,当值不为0的时候就不在走block
方法,也就不会再创建实例。通过一下源码大致分析dispatch_once
的流程:
// dispatch_once_t 的定义
typedef long dispatch_once_t;
// dispatch_once 的定义
void dispatch_once(dispatch_once_t *val, void (^block)(void)){
struct Block_basic *bb = (void *)block;
dispatch_once_f(val, block, (void *)bb->Block_invoke);
}
// 核心点dispatch_once_f函数
void dispatch_once_f(dispatch_once_t *val, void *ctxt, void (*func)(void *)){
volatile long *vval = val;
if (dispatch_atomic_cmpxchg(val, 0l, 1l)) {
func(ctxt);
dispatch_atomic_barrier();
*val = ~0l;
} else {
do {
_dispatch_hardware_pause();
} while (*vval != ~0l);
dispatch_atomic_barrier();
}
}
dispatch_atomic_cmpxchg(val, 0l, 1l)
函数判断val
的值是否等于0
,等于则将val
值赋为1
,返回true
,否则返回false
。
在多线程环境中,如果某一个线程Thread1
首次进入dispatch_once_f
,*val
为0
,这个时候直接将其设为1
,然后执行func(ctxt);
方法(这个就是我们block
里边的代码),然后调用dispatch_atomic_barrier
(这函数干啥暂时没有搞懂,网搜了一下说是一个编译器操作,意思为前后指令的顺序不能颠倒.这里是防止编译器优化将原本的指令语义破坏),最后将*val
的值修改为~0l
(非零)。
如果其它的线程也进入dispatch_once_f
,那么这个时候if的判断返回false
,就进入else
分支,于是执行了do~while
空循环,一直等待。_dispatch_hardware_pause
(这个函数具体干啥暂时未知,网上说有助于提高性能和节省CPU耗电,延迟空等),直到首个线程已经将func(ctxt);
执行完毕且将*val
修改为~0l
,调用dispatch_atomic_barrier
后退出。所以多线程的时候block
也是无法同时执行的,这就保证了在dispatch_once_f
的block
的执行的唯一性,生成的单例也是唯一的。
如何创建唯一的单例实例
上面两种方式创建的单例,其实在系统中并不是唯一的,并不唯一的意思是 当前类可以通过其他方式创建实例。 并不是说 通过shareManager
方法获取的实例不是唯一的。注意区分喔(tip:系统实现的单例其实也是可以通过alloc、new
等方式去重新获取实例的)。
还是可以通过alloc 、new、copy、mutableCopy
等方法获取到当前类的实例。
那么如何避免通过alloc 、new、copy、mutableCopy
获取到实例呢?也有两种实现方式。
1、通过 NS_UNAVAILABLE
实现
我们把能创建实例的方法全部加入 NS_UNAVAILABLE
。
+ (instancetype)alloc NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (id)copy NS_UNAVAILABLE;
- (id)mutableCopy NS_UNAVAILABLE;
这样定义了之后,在外部通过alloc 、new、copy、mutableCopy
获取实例系统会报错找不到方法(tip:假如当前类没有实现NSCopying, NSMutableCopying
协议时,可以不用限制copy、mutableCopy
)。
2、通过重写方法的方式实现
// 重写+ (instancetype)allocWithZone:(struct _NSZone *)zone; 方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [UserInfo shareManager];
}
// 注意:重写+ (instancetype)allocWithZone:(struct _NSZone *)zone; 方法后,需要调用父类allocWithZone方法来分配空间
// GCD创建方式需要改写为
+ (instancetype)shareManager{
static dispatch_once_t onceToken;
static UserInfo *manager;
dispatch_once(&onceToken, ^{
if (manager == nil) {
manager = [[super allocWithZone:NULL] init];
}
});
return manager;
}
// 如果实现了NSCopying, NSMutableCopying协议,还需要重写copy、mutableCopy方法
- (id)copyWithZone:(NSZone *)zone {
return [UserInfo shareManager];
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [UserInfo shareManager];
}
注意:通过 NS_UNAVAILABLE
方式实现的,也不一定是无法获取当前类其它实例,还是可以通过runtime
获取:
UserInfo *allocInfo = [UserInfo performSelector:@selector(alloc)];
通过上述的方式,还是可以获取。
单例使用注意
初始化要尽量简单,不要太复杂;
单例尽量考虑使用场景,不要随意实现单例,单例一旦初始化就会一直占着资源不能释放,造成资源浪费。
网友评论