单例 的意思就是确保在程序运行过程中只创建一个对象实例,而可以被多次使用。如我们常见的网络请求类、工具类、和其他管理类等。作为一个iOS开发者,我们经常和单例打交道,比如:[NSUserDefaults standerUserDefaults]
, [UIApplication sharedApplication]
,[UIScreen mainScreen]
, [NSFileManager defaultManager]
返回的都是单例对象。
我们在开源项目、苹果示例代码能见到无数使用单例的例子。Xcode 甚至有默认的 "Dispatch Once" 代码片段,使我们可以非常简单地在代码中添加一个单例。
一、单例的作用
- 它可以保证某个类在程序运行过程中最多只有一个实例,节省内存开销。如果某个对象需要被多个其它对象使用,那可以考虑使用单例,因为这样该类只使用一份内存资源。
注意点!!!! 单例应该只用来保存全局的状态,并且不能和任何作用域绑定。如果这些状态的作用域比一个完整的应用程序的生命周期要短,那么这个状态就不应该使用单例来管理。因为单例从创建后到彻底关闭程序前都会一直存在,如果过多的创建单例无疑浪费系统资源和影响系统效率。
二、单例的三要点
- 单例模式的三个要点:
1. 该类有且只有一个实例;(类的构造方法是私有的我们只需要重写allocWithZone
方法,让初始化操作只执行一次)
2. 该类必须能够自行创建这个实例;(类提供一个类方法产生对象)
3. 该类必须能够自行向整个系统提供这个实例。(类中有一个私有的自己对象我们可以在.m文件中定义一个属性即可)
三、单例的实现步骤
1. 为单例对象创建一个静态实例,可以写成全局的,也可以在类方法里面实现,并初始化为nil;
2. 提供一个类方法,方便外界访问;
3. 重写allocWithZone
方法,用来保证其他人直接使用alloc和init试图获得一个新实力的时候不产生一个新实例;
4. 重写-copyWithZone
方法和-MutableCopyWithZone
方法
一般创建对象的步骤分为申请内存(alloc)、初始化(init)这两个步骤,我们要确保对象的唯一性,所以要在第一步拦截它。当我们调用alloc方法时,oc内部会调用allocWithZone这个方法来申请内存,我们覆写这个方法,然后在这个方法中调用shareInstance方法返回单例对象,这样就可以达到我们的目的。拷贝对象也是同样的原理,覆写copyWithZone方法,然后在这个方法中调用shareInstance方法返回单例对象。
#import "Tools.h"
@implementation Tools
// 创建静态对象 防止外部访问
static Tools *_instance;
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
@synchronized (self) {
// 为了防止多线程同时访问对象,造成多次分配内存空间,所以要加上线程锁
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
return _instance;
}
// 为了使实例易于外界访问 我们一般提供一个类方法
// 类方法命名规范 share类名|default类名|类名
+(instancetype)shareTools
{
//return _instance;
// 最好用self 用Tools他的子类调用时会出现错误
return [[self alloc]init];
}
// 为了严谨,也要重写copyWithZone 和 mutableCopyWithZone
-(id)copyWithZone:(NSZone *)zone
{
return _instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
return _instance;
}
也可以使用Xcode 默认的dispatch_once
代码片段用 GCD的方式创建:
@implementation Singleton
static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedSingleton{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
return _instance;
}
@end
四、使用单例的注意事项
- 单例给我们带来方便的同时也有一定的副作用,因为单例对象一旦创建,对象指针是保存在静态区的,单例对象在堆中分配的内存空间只有在程序终止后才会释放,过多的单例必定会增大我们消耗的内存
- 不要做断开单例类对象与类中静态引用的危险操作。
- 多线程使用单例使用共享资源时,注意线程安全问题。