单例模式定义
单例模式:保证一个类有且仅有一个实例,并提供一个访问ta的全局访问点(易于外界访问)
在 iOS 中,系统默认就有许多支持单例方法的地方,例如通知中心、应用管理、本地保存等,但是一般都属于非严格的单例模式,也就是你可以使用
[UIApplication sharedApplication]
来获取单例对象,也可以通过[UIApplication new]
生成一个新的对象,而如果按照准确的单例定义来说,依旧以UIApplication
类为例,[UIApplication new]
与[UIApplication sharedApplication]
应该在任何时候返回的对象都是相同的。也就是说无论你怎么操作,只会存在一个实例,不会创建其他的副本内容
什么时候使用单例?
- 类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问,比如工厂方法
- 这个唯一的实例只能通过子类化进行扩展,而且扩展的对象不会破坏客户端代码
- 为了节省内存,部分类可以使用单例模式,例如多处使用的图片等
单例类图如下:
不同客户端调用Singleton
的shareInstance
方法生成的只有唯一的一个Singleton
对象
本质就是让不同的指针指向同一块内存
具体代码实现
单例模式一般会封装一个静态属性(私有静态变量),并提供静态实例的创建方法(公有静态变量)
单例通常有两种写法:
- 一般写法
@interface Singleton : NSObject
//获得单例实例方法
+ (instancetype)shareInstance;
@end
@implementation Singleton
//私有静态变量
static Singleton* instanceVariable = nil;
+ (instancetype)shareInstance {
if (!instanceVariable) {
instanceVariable = [[Singleton alloc]init];
}
return instanceVariable;
}
@end
在成员变量部分用static
创建静态对象,保证返回对象的唯一性
但这样写会在多线程情况下
导致重复创建,这样会存在两块内存,不安全。通常会添加一个同步锁
@implementation Singleton
static Singleton* instanceVariable = nil;
+ (instancetype)shareInstance {
@synchronized (self) {
if (!instanceVariable) {
instanceVariable = [[Singleton alloc]init];
}
}
return instanceVariable;
}
@end
继续优化,为防止每次读取造成卡顿,再在外层加一层判断
@implementation Singleton
static Singleton* instanceVariable = nil;
+ (instancetype)sharedInstance {
//判断对象是否存在,若不存在,创建对象
if (!instanceVariable) {
//线程安全:仅仅只让一个线程进行,一个线程结束后,才能进行另一个线程
@synchronized (self) {
if (!instanceVariable) {
instanceVariable = [[Singleton alloc] init];
}
}
}
return instanceVariable;
}
@end
接下来测试一下整个程序中是否有仅含有一个单例:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Singleton* singleV1 = [Singleton sharedInstance];
NSLog(@"%p", singleV1);
Singleton* singleV2 = [Singleton sharedInstance];
NSLog(@"%p", singleV2);
}
return 0;
}
运行结果:
可以看出声明的两个指针都指向同一块内存(地址),这表明自始至终仅有一个对象(实例)存在
- GCD写法
GCD
(Grand Central Dispatch)是一种基于C语言的多线程访问技术,GCD写法是苹果官方提供的单例的实现写法
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instanceVariable = [[Singleton alloc] init];
//instanceVariable = [[self alloc] init];
});
return instanceVariable;
}
这段代码中GCD提供的
dispatch_once
函数,作用是在整个应用程序生命周期中只执行一次代码块(^{ … })dispatch_once_t
结构体,使用时需要将GCD地址(也是为了保证唯一性)传给dispatch_once
函数
dispatch_once
函数不仅意味着代码仅会被运行一次,而且意味着此运行还是线程同步的
为了更形象地理解以上这段代码,我们利用NSLog
函数打印消息来看一下:
void haha(void) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"Hahahahah233");
});
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
haha();
haha();
haha();
haha();
haha();
}
return 0;
}
用C风格函数封装打印Hahahahah233
,然后在主函数里调用5次,看看结果
调了5遍,只打印了1遍,说明GCD提供的这段代码可以将我们想实现的代码只实现1次
单例模式的缺点
这是需要了解的点
- 单例实例一旦创建,对象指针是保存在静态区的,那么在堆区分配空间只有在应用程序终止后才会被释放。单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置时并不能销毁,在闲置时也消耗了系统内存资源
- 单例不能被继承,不能有子类,因为它们共享一份资源,有子类单例模式就没有意义了
为什么要用static修饰私有变量?
在单例模式中,我们希望只创建一个类的实例,并且在整个应用程序生命周期内共享这个实例。通过使用static
修饰全局变量,我们可以确保该变量只被初始化一次,并且在后续的访问中保持其状态
另外,使用static
修饰全局变量还有几个好处:
- 静态变量对外部不可见,无法直接访问或修改,可以保护全局变量的安全性
- 静态变量不会被其他类或实例所覆盖,确保单例实例的唯一性
- 静态变量属于类级别,可以通过类名直接访问,方便全局使用
总结
单例模式还有很多实现方式,但现在在主流的iOS开发中都是用GCD写法的,其他实现方法了解即可,可以作为我们理解单例模式的辅助