单例模式
优点:
- 单例可以保证系统中该类有且只有一个实例,便于外界访问,对于项目中个别场景的传值,存储状态更加方便。
- 程序出了问题,可以快速定位问题所在
- 由于整个程序中只存在一个对象,因此节省了内存资源,提高程序的运行效率
缺点: - 单例不能被继承,不能有子类,因为它们是共享一份资源的
- 单例实例一旦创建,对象指针是保存在静态区的,那么在堆区分配空间只有在应用程序终止后才能被释放。单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置的时候不能被销毁,在闲置的时候也消耗了系统的内存资源
两种模式:1、懒汉模式;2、饿汉模式
- 懒汉模式:第一次用到单例对象的时候再创建
- 饿汉模式:一进入程序就创建一个单例对象
以上两模式的优缺点
1、时间和空间
比较上面两种写法:懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
2、线程安全
(1)从线程安全性上讲,不加同步的懒汉式是线程不安全的
(2)饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。
单例模式的创建
懒汉模式
在最开始的OC学习中,我们学习了一个简单的基本的单例模式的创建:首先定义一个全局变量,类型为我们的类,并给这个全局变量赋值为nil;
当我们获取该类的实例对象的时候,程序会判断该全局变量是不是nil,是就创建该实例并将实例对象赋给全局变量,然后返回该实例对象,否则就直接返回该全局变量。
#import "AModel.h"
static id _instance = nil;
@implementation AModel
+ (id)sharInstance {
if (_instance == nil) {
_instance = [[self alloc] init];
}
return _instance;
}
@end
但是上面的这个单例模式还是存在一些问题,比如假如到了多线程的环境里,多个进程同时访问单例,该单例模式也有可能返回不同的对象
- 因此这里我们就要用到***dispatch_once(dispatch_once_t *predicate,dispatch_block_t block);***方法来保证线程安全,因此我们可以这么写:
#import "AModel.h"
static id _instance = nil;
@implementation AModel
+ (id)sharInstance {
//定义一个dispatch_once_t类型的名为onceToken的静态全局变量,确保它在运行时只会被初始化一次
static dispatch_once_t onceToken;
//调用dispatch_once函数,该函数用于确保一个代码块只执行一次。
//它接受两个参数:一个指向dispatch_once_t类型变量的指针,以及一个表示需要执行一次的代码块的匿名函数
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
@end
- 还有一种防止多线程无法实现单例的方法:就是给代码加一个锁
#import "AModel.h"
static id _instance = nil;
@implementation AModel
+ (id)sharInstance {
return [[self alloc] init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
//@synchronized的作用是创建一个互斥锁,保证此时没有其他线程对self对象进行修改,保证代码的安全性
@synchronized (self) {
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
@end
使用以上的方法,可以使我们通过调用该方法来初始化实例对象,但是如果我们使用 [[xxx alloc] init] 方法来初始化该对象,就会发现返回的还是不同的对象,这是因为使用 [[xxx alloc] init] 方法实际上alloc的过程是调用了allocWithZone方法,所以用不了我们自己定义的初始化方法,因此想要真正完成单例模式,我们还应该重写allocWithZone方法,使alloc的时候返回唯一实例;还应该重写 copyWithZone:方法,避免实例对象的 copy 操作导致创建新的对象:
#import "AModel.h"
static id _instance = nil;
@implementation AModel
+ (id)sharInstance {
return [[self alloc] init];
}
//将我们原先写在自定义初始化方法中的内容写到allocWithZone中
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
//重写 copyWithZone:方法,避免实例对象的 copy 操作导致创建新的对象
-(instancetype)copyWithZone:(NSZone *)zone
{
//由于是对象方法,说明可能存在_instance对象,直接返回即可
return _instance;
}
@end
因为我们在这里已经重写了allocWithZone方法,如果再直接使用[[self alloc] init]就会使程序陷入alloc的死循环,因此这里创建实例对象的时候使用父类的allocWithZone方法
GCD简化单例(MRC)
在 MRC 环境中,我们需要考虑如果创建出来的单例对象,被手动 release 了怎么办?所以我们在设计单例模式的时候,需要考虑这种情况。如下:
- retain,单例对象创建后,全局只有一个对象,所以一定要保证 retain 后仍然是自身,且引用计数不变
- release,由于只有一个对象,被 release 后不能被释放掉,所以 release 操作需要拦截
- autorelease,与 release 一样
- retainCount,始终保证引用计数器为1
所以在 MRC 环境中,设计单例模式时,还需要重写下面四个方法
//重写 retain 方法,不作计数器加1的操作
-(instancetype)retain
{
return _instance;
}
//重写 release 方法,不做任何操作
-(void)release
{
}
//重写 autorelease 方法,返回自身
-(instancetype)autorelease
{
return _instance;
}
//重写 retainCount 方法,返回1
-(NSUInteger)retainCount
{
return 1;
}
饿汉模式
饿汉模式就是当类第一次被创建的时候就去创建实例对象,并保存在_instance中,由于第一次加载就创建,内存从程序开始运行的时候就分配了,不适合移动设备。
在使用饿汉模式之前,我们先说load方法和initilized方法。
load方法
initilized方法
static id _instance;
@implementation EHanModel
//当类加载到OC运行环境中(内存)时,就会调用一次(一个类只会加载一次)
+ (void) load {
_instance = [[self alloc] init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
@synchronized (self) {
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
+ (id)sharInstance {
return [[self alloc] init];
}
@end
宏实现单例
由于单例的h文件和m文件一成不变,所以可以抽成宏定义。抽成宏定义需要注意
- 宏定义后面如果要替换字符,需要用##拼接
#define SoundToolH(name) +(instancetype)shared##name;
//调用宏定义SoundToolH(MusicTool)时,就相当于
+(instancetype)sharedMusicTool;
- 宏定义后边如果出现换行,需要用符号“ \ ” 来标记下一行也是宏定义的部分,但最后一行末尾不需要
#define SoundToolM(name) \
static id _instance;\
+(instancetype)shared##name\
{\
dispatch_once_t onceToken = NULL;\
dispatch_once(&onceToken)\
{\
_instance = [self alloc]init];\
}\
}