1、单例的定义
2、单例的作用
3、单例的使用场合
4、优点
- 单例模式可以保证系统中一个类只有一个实例,且该实例易于外界访问,从而可以方便的控制实例的个数,节约系统资源。
- 单例对象一旦创建,对象指针是保存在静态区的。单例对象在堆中分配的内存空间,会在应用程序终止后才会被释放。
5、缺点
6、注意:
7、创建单例对象的步骤
1、在类内部定义一个static修饰的全局变量。
2、提供一个类方法 ,方法名:+share类名 或者 +default类名,方便外界访问。
3、重写系统的 +allocWithZone:方法,保证一个单例对象分配一次内存空间,实现一个类只有一个实例。
4、重写–copyWithZone方法和-mutableCopyWithZone方法,遵守NSCopying协议、NSMutableCopying协议,可以通过copy、mutableCopy方式创建对象。
#import "Tools.h"
@implementation Tools
//ARC下创建单例对象的步骤
//创建私有的静态对象,防止外部访问
static Tools *_instance;
static dispatch_once_t onceToken;
//重写 allocWithZone: 方法,保证单例对象只能分配一次内存空间
+(instancetype)allocWithZone:(struct _NSZone *)zone {
//为了防止多线程访问对象,造成多次分配内存空间,所以要加上线程锁(互斥锁)
// @synchronized (self) {
// if (_instance == nil) {
// _instance = [super allocWithZone:zone];
// }
// return _instance;
// }
//使用GCD创建一次性对象也能实现单例
dispatch_once(&onceToken, ^{
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
});
return _instance;
}
//定义类方法,易于外界访问
+ (instancetype)shareTools {
return [[self alloc]init];
}
// 为了严谨,也要重写copyWithZone 和 mutableCopyWithZone
-(id)copyWithZone:(NSZone *)zone {
return _instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone {
return _instance;
}
//调用
Tools *tools = [Tools shareTools];
Tools *tools2 = [[Tools alloc]init];
NSLog(@"tools = %@",tools);
NSLog(@"tools2 = %@",tools2);
输出显示
可以看出两种初始化方法返回的指针地址相同,创建出来的是同一个对象。因为单例对象的初始化方法 alloc 方法底层调用的就是 +allocWithZone:方法。
问题:因为单例对象使用 static 修饰,被存储在静态区域。该对象只能在程序终止时才会被释放。如果我要提前释放单例对象该怎么做呢?
Tools *tools = [Tools shareTools];
tools.name = @"step1 name";
NSLog(@"step1---%@",tools);
NSLog(@"step1 defaultValue = %@, name = %@", tools.defaultValue, tools.name);
tools = nil;
NSLog(@"step2---%@",tools);
NSLog(@"step2 defaultValue = %@, name = %@", tools.defaultValue, tools.name);
tools = [Tools shareTools];
NSLog(@"step3---%@",tools);
NSLog(@"step3 defaultValue = %@, name = %@", tools.defaultValue, tools.name);
打印的数据
按照正常的一个认知,将实例对象instance置为nil后,重新实例化后,应该是一个新的对象。但是丛云性能结果可以看出,将单例对象设置为 nil,再次创建对象,还是指向的同一块内存空间,说明并没有创建新的对象。表明用这中方式来释放对象是不可取的。
那么是什么原因造成的呢?进入到dispatch_once的源码实现中去探究下
dispatch_once源码
注意dispatch_once源码中的红线部分,dispatch_once_t *predicate; dispatch_once_t又是什么呢?继续看源码
dispatch_once_t源码
- 通过源码我们可以清晰的知道,dispatch_once_t是一个long类型的变量,初始化必须为0;
- 至此,我们可以清晰的了解到,dispatch_once的实现逻辑;
- dispatch_once主要是根据long类型的值决定怎么去执行代码(所以dispatch_once_t需要声明为static,保证全局只有1个)
- 当值为0时,执行dispatch_once的block代码
- 当值为-1时,跳过dispatch_once的block代码
- 当值为其他值时,线程被阻塞,等待其值改变;
- 当dispach_once的block执行完成后,将long类型的值设置为-1.其他线程不在阻塞,跳过block。下周再调用shareInstance的时候,block已经为-1。直接跳过block。
+(instancetype)allocWithZone:(struct _NSZone *)zone {
//为了防止多线程访问对象,造成多次分配内存空间,所以要加上线程锁(互斥锁)
// @synchronized (self) {
// if (_instance == nil) {
// _instance = [super allocWithZone: zone];
// }
// return _instance;
// }
//使用 GCD 创建一次对象也可以
NSLog(@"1---%ld",onceToken); //输出:0(onceToken)
dispatch_once(&onceToken, ^{
if (_instance == nil) {
_instance = [super allocWithZone: zone];
_instance.defaultValue = @"init defaultValue";
NSLog(@"2---%ld",onceToken); //输出:256(一个很大的数,此时线程阻塞状态)
}
});
NSLog(@"3---%ld",onceToken); //输出:-1 跳过dispatch_once的block代码
return _instance;
}
onceToken 的运行结果
了解了dispatch_once的实现原理后,将实例instance=ni后,再次实例化后,得到的还是同样的结果,那么应该如何修改呢?
单例的完整实现
+(instancetype)allocWithZone:(struct _NSZone *)zone {
//使用 GCD 创建一次对象也可以
NSLog(@"1---%ld",onceToken); //输出:0(onceToken)
dispatch_once(&onceToken, ^{
if (_instance == nil) {
_instance = [super allocWithZone: zone];
_instance.defaultValue = @"init defaultValue";
NSLog(@"2---%ld",onceToken); //输出:256(一个很大的数,此时线程阻塞状态)
}
});
NSLog(@"3---%ld",onceToken); //输出:-1 跳过dispatch_once的block代码
return _instance;
}
//手动创建一个方法,设置onceToken为 0
-(void)clear {
onceToken = 0;
//将全局静态单例对象设置为 nil
_instance = nil;
}
调用单例对象
Tools *tools = [Tools shareTools];
tools.name = @"step1 name";
NSLog(@"step1---%@", tools);
NSLog(@"step1 defaultValue = %@, name = %@", tools.defaultValue, tools.name);
[tools clear];
// tools = nil; //后面没有使用 tools 对象的话,在大括号执行完毕时, tools 也会被释放
NSLog(@"step2---%@",tools);
tools = [Tools shareTools];
NSLog(@"step4---%@",tools);
NSLog(@"step4 defaultValue = %@, name = %@", tools.defaultValue, tools.name);
打印结果:
这样操作正确释放了单例对象,成功创建了新的单例对象。
总结:一般定义了单例,就是需要整个类中只创建一个单例对象,可以给到全局使用,直到程序终止该对象才被释放。单例不能继承,也不能对类进行扩展。因此要正确使用单例对象。
原文地址:https://blog.csdn.net/same_life/article/details/127306803
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_6015.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!