1.什么是单例
确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点。
可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源。
2.单例能做什么
- 在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次),这个类创建出来的对象永远只有一个。
使用场景:
- 工具类
- 频繁创建的类(比如登录页,未登录状态下,某些触发条件会调出登录页)
3.单例的实现
3.1 实现的方式
1> 命名方式:
share+类名 | default + 类名 | share | 类名
如:+(instancetype)sharedSingle;
2> 实现代码
- 未使用单例:
RTTool *tool_1 = [[RTTool alloc] init];
RTTool *tool_2 = [[RTTool alloc] init];
RTTool *tool_3 = [[RTTool alloc] init];
NSLog(@"\n tool_1:%p \n tool_2:%p \n tool_3:%p",tool_1,tool_2,tool_3);
打印对象地址:
No Single.png
可以观察到,三份地址都不同。
- 使用单例:
注:还不完善
互斥锁:
static RTTool *_instance; //提供全局静态变量(作用域结束,本身不可修改)
/// 调用alloc真正实现内存分配的方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
@synchronized (self) {
if(_instance == nil) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
GCD一次代码:
static RTTool *_instance; //提供全局静态变量
/// 调用alloc真正实现内存分配的方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
// 参数取地址的好处?
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
打印对象地址:
可以观察到,三份地址
完全相同
。
提供可供外界访问的便捷方法
static RTTool *_instance; //提供全局静态变量
+ (instancetype)shareManager {
static dispatch_once_t onceToken;
// 参数取地址的好处?
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
//+ (instancetype)shareManager {
// return [[self alloc] init];
//}
然后我们就可以这样调用
RTTool *tool_1 = [[RTTool alloc] init];
RTTool *tool_2 = [[RTTool alloc] init];
RTTool *tool_3 = [[RTTool alloc] init];
RTTool *tool_4 = [RTTool shareManager]; //便捷访问方法
NSLog(@"\n tool_1:%p \n tool_2:%p \n tool_3:%p \n tool_4:%p",tool_1,tool_2,tool_3,tool_4);
完整的Singleton实现方式
需要遵守拷贝协议
@interface RTTool()<NSCopying,NSMutableCopying>
static RTTool *_instance; //提供全局静态变量
+ (instancetype)shareManager {
static dispatch_once_t onceToken;
// 参数取地址的好处?
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
//+ (instancetype)shareManager {
// return [[self alloc] init];
//}
/// MARK: - 拷贝操作的时候,我们的_instance已经存在了,直接返回给调用方即可
- (id)copyWithZone:(NSZone *)zone {
return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return _instance;
}
调用及结果:
Single.png
3.2 static的作用
编译时分配内存,程序结束时释放内存单元。
3.2.1 作用于变量
作用范围:本文件内部有效/本作用域有效。
- 1.局部变量
改变变量的存储方式(生命期),使变量成为静态的局部变量。即编译时就为变量分配内存,直到程序退出才释放存储单元。
延长局部变量的生命周期,作用范围为局部变量作用域。
好处:使得该局部变量有记忆功能,可以记忆上次的数据,不过由于仍是局部变量,因而只能在代码块内部使用(作用域不变)。
{
static int a; //作用范围为本代码块结束
}
- 2.全局变量
限制某些外部变量的作用域,使其只在本文件中有效,而不能被其他文件引用。(本文单例模式static全局变量的作用即此)
3.2.2 作用于函数
使用static用于函数定义时,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可。
3.3 @synchronized作用
@synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改,保证代码的安全性。也就是包装这段代码是原子性的,安全的。这个是objective-c的一个锁定令牌,防止self对象在同一时间内被其他线程访问,起到保护线程安全的作用。
更多使用细节可查看链接。
我们在此处不推荐使用@synchronized的原因(更多原因点击查看):
- 1.@synchronized速度慢,消耗CPU资源比较大。
- 2.外部调用可能导致死锁。
3.4 dispatch_once为什么能保证线程安全,参数取地址的好处
-
dispatch_once为什么能保证线程安全
核心函数dispatch_once_f:
-
dispatch_once为什么能保证线程安全
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); // block真正执行
dispatch_atomic_barrier();
*val = ~0l;
}
else
{
do
{
_dispatch_hardware_pause();
} while (*vval != ~0l);
dispatch_atomic_barrier();
}
}
参见dispatch_once源码,其核心函数dispatch_once_f源码分析:
某一个线程A首次进入dispatch_once_f,执行传入dispatch_once_f的block,然后调用dispatch_atomic_barrier;dispatch_atomic_barrier是一种内存屏障,串行化读写操作和解决顺序一致性问题,等屏障前的指令执行完了,屏障后的指令才能开始执行。
在首个线程A执行block的过程中,如果其它的线程也进入dispatch_once_f,执行方法就会走到else分支,调用_dispatch_hardware_pause,它干的事情就是延迟空等,这样做有助于提高性能和节省CPU耗电。
首个线程已经将block执行完毕,dispatch_atomic_barrier退出,这就保证了在dispatch_once_f的block的执行的唯一性,生成的单例也是唯一的。
注:dispatch_once的缺点
- 2.参数取地址的好处
3.5 NSCopying,NSMutableCopying
这部分很简单详情见链接
4.单例的优缺点
更多说明:参看链接
优点 :
1、由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
2、由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决;
3、单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
4、单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
缺点 :
1、单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
2、单例模式与单一职责原则有冲突。
5.总结
除单一设计原则与允许可扩展需求外,单例设计在性能优化以及减少内存占用方面具有非常良好的表现。
参考资源
- static: https://www.jianshu.com/p/512b97c89297
- @synchronized:http://www.cocoachina.com/articles/18279
- 多线程安全: https://www.jianshu.com/p/ef3f77c8b320
- dispatch_once: 1.https://developer.apple.com/documentation/dispatch/1447169-dispatch_once?language=occ
2.https://opensource.apple.com/source/libdispatch/libdispatch-84.5/src/once.c.auto.html
3.http://lingyuncxb.com/2018/02/01/GCD%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%902%20%E2%80%94%E2%80%94%20dispatch-once%E7%AF%87/
https://blog.csdn.net/u014600626/article/details/102862777
网友评论