单例模式
优点:
- 单例可以保证系统中该类有且只有一个实例,便于外界访问,对于项目中个别场景的传值,存储状态更加方便。
- 程序出了问题,可以快速定位问题所在
- 由于整个程序中只存在一个对象,因此节省了内存资源,提高程序的运行效率
缺点: - 单例不能被继承,不能有子类,因为它们是共享一份资源的
- 单例实例一旦创建,对象指针是保存在静态区的,那么在堆区分配空间只有在应用程序终止后才能被释放。单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置的时候不能被销毁,在闲置的时候也消耗了系统的内存资源
两种模式:1、懒汉模式;2、饿汉模式
- 懒汉模式:第一次用到单例对象的时候再创建
- 饿汉模式:一进入程序就创建一个单例对象
以上两模式的优缺点
1、时间和空间
比较上面两种写法:懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
2、线程安全
(1)从线程安全性上讲,不加同步的懒汉式是线程不安全的
(2)饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。
单例模式的创建
懒汉模式
在最开始的OC学习中,我们学习了一个简单的基本的单例模式的创建:首先定义一个全局变量,类型为我们的类,并给这个全局变量赋值为nil;
当我们获取该类的实例对象的时候,程序会判断该全局变量是不是nil,是就创建该实例并将实例对象赋给全局变量,然后返回该实例对象,否则就直接返回该全局变量。
objectivec
#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);***方法来保证线程安全,因此我们可以这么写:
objectivec
#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
- 还有一种防止多线程无法实现单例的方法:就是给代码加一个锁
objectivec
#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 操作导致创建新的对象:
objectivec
#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方法
为什么全局变量要使用 static?
① static修饰局部变量
其生命周期与全局变量相同,直到程序结束,只有一份内存空间
作用域不变
② static修饰全局变量
只有一份内存空间
全局变量,在其他文件中,可以通过 extern id _instance来声明,然后直接在其他文件中调用。用 static 修饰后 在其他文件不能通过 extern id _instance 声明后 引用
GCD简化单例(MRC)
在 MRC 环境中,我们需要考虑如果创建出来的单例对象,被手动 release 了怎么办?所以我们在设计单例模式的时候,需要考虑这种情况。如下:
- retain,单例对象创建后,全局只有一个对象,所以一定要保证 retain 后仍然是自身,且引用计数不变
- release,由于只有一个对象,被 release 后不能被释放掉,所以 release 操作需要拦截
- autorelease,与 release 一样
- retainCount,始终保证引用计数器为1
所以在 MRC 环境中,设计单例模式时,还需要重写下面四个方法
objectivec
//重写 retain 方法,不作计数器加1的操作
-(instancetype)retain
{
return _instance;
}
//重写 release 方法,不做任何操作
-(void)release
{
}
//重写 autorelease 方法,返回自身
-(instancetype)autorelease
{
return _instance;
}
//重写 retainCount 方法,返回1
-(NSUInteger)retainCount
{
return 1;
}
饿汉模式
饿汉模式就是当类第一次被创建的时候就去创建实例对象,并保存在_instance中,由于第一次加载就创建,内存从程序开始运行的时候就分配了,不适合移动设备。
在使用饿汉模式之前,我们先说load方法和initilized方法。
load方法
①当程序刚开始运行的时候,所有的类都会加载到内存中(不管这个类有没有使用),此时就会调用 load 方法
②如果某种操作想要在程序运行的过程中只执行一次,那么这个操作就可以放到 load 方法中
③基于第二点,我们的饿汉模式的单例对象创建就放在 load 方法中
initilized方法
①当类第一次被使用的时候调用(比如,调用类的方法)。
②如果子类没有重写该方法,那么父类的initialized方法可能会被执行多次。所以饿汉模式不能使用这个方法
饿汉模式在类加载的时候就会创建类的实例,而在iOS中,类的实例是在调用init方法时创建的。因此,在饿汉模式中,如果使用init方法来创建实例,就会导致无法创建实例的情况发生。因此,在iOS中,饿汉模式不能使用initilized方法来创建实例。
objectivec
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文件一成不变,所以可以抽成宏定义。抽成宏定义需要注意
- 宏定义后面如果要替换字符,需要用##拼接
objectivec
#define SoundToolH(name) +(instancetype)shared##name;
//调用宏定义SoundToolH(MusicTool)时,就相当于
+(instancetype)sharedMusicTool;
- 宏定义后边如果出现换行,需要用符号" \ " 来标记下一行也是宏定义的部分,但最后一行末尾不需要
objectivec
#define SoundToolM(name) \
static id _instance;\
+(instancetype)shared##name\
{\
dispatch_once_t onceToken = NULL;\
dispatch_once(&onceToken)\
{\
_instance = [self alloc]init];\
}\
}