一、单例模式的概念
如果一个类无论多少次都只能创建一个对象并提供一个类方法供全局调用,在编译时初始化这个类,然后一直保存在内存中,到程序退出时由系统自动释放这部分内存,那么这个类就被称为单例类。
二、单例模式的优缺点
- 优点
1.由于单例可以保证系统中该类有且只有一个实例,所以很便于外界访问。
2.由于无论多少次都只创建一个对象,所以单例可以节省系统的内存开销。 - 缺点
1.单例的可扩展性比较差,不易于进行重写或扩展,但是可以进行分类。
2.单例类一旦创建实例对象,该对象指针被保存在静态区,在堆区申请分配的内存空间只有当程序运行结束后才会被释放掉。
三、单例模式的生命周期
由于单例类一旦创建实例对象,该对象指针被保存在静态区,所以该对象在堆区申请分配的内存空间只有当程序运行结束后才会被释放掉。
四、单例模式的实现
为了防止创建完单例类后,在外部调用时出现实例对象不一致的情况,需要做到以下几点:
1.防止调用 alloc init 引起错误
2.防止调用 new 引起错误
3.防止调用 copy 引起错误
4.防止调用 mutableCopy 引起错误
五、单例模式的两种方式
1.懒汉模式
运行程序时先不进行单例类的创建,当需要用到的时候再进行创建,这种方式也是单例模式下比较常用的方式。
具体实现代码如下:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Singleton : NSObject<NSCopying, NSMutableCopying>
+(instancetype)mySingleton;
@end
NS_ASSUME_NONNULL_END
#import "Singleton.h"
@implementation Singleton
static id instance = nil;
+(instancetype)allocWithZone:(struct _NSZone *)zone {
if (instance == nil) {//防止频繁加锁
@synchronized (self) {
if (instance == nil) {//防止创建多次
instance = [super allocWithZone: zone];
}
}
}
return instance;
}
+(instancetype)mySingleton {
if (instance == nil) {
@synchronized(self) {
if (instance == nil) {
instance = [[self alloc] init];
}
}
}
return instance;
}
- (id)copyWithZone:(NSZone *)zone {
return instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return instance;
}
@end
#import <Foundation/Foundation.h>
#import "Singleton.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Singleton* singleton = [Singleton mySingleton];
Singleton* copySingleton = [singleton copy];
Singleton* mutableCopySingleton = [singleton mutableCopy];
Singleton* allocSingleton = [[Singleton alloc] init];
NSLog(@"%d", singleton == copySingleton);
NSLog(@"%d", singleton == mutableCopySingleton);
NSLog(@"%d", singleton == allocSingleton);
}
return 0;
}
运行结果如下:
可以看到创建的这几个实例对象都为同一个实例对象。
2.饿汉模式
程序一旦运行起来就直接创建单例类,这种方式一般不常用。
具体实现代码如下:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Singleton : NSObject<NSCopying, NSMutableCopying>
+(instancetype)mySingleton;
@end
NS_ASSUME_NONNULL_END
#import "Singleton.h"
@implementation Singleton
static id instance = nil;
+ (void)load{
instance = [[self alloc] init];
}
+(instancetype)allocWithZone:(struct _NSZone *)zone {
if (instance == nil) {
@synchronized (self) {
if (instance == nil) {
instance = [super allocWithZone: zone];
}
}
}
return instance;
}
+(instancetype)mySingleton {
if (instance == nil) {
@synchronized(self) {
if (instance == nil) {
instance = [[self alloc] init];
}
}
}
return instance;
}
- (id)copyWithZone:(NSZone *)zone {
return instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return instance;
}
@end
#import <Foundation/Foundation.h>
#import "Singleton.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Singleton* singleton = [Singleton mySingleton];
Singleton* copySingleton = [singleton copy];
Singleton* mutableCopySingleton = [singleton mutableCopy];
Singleton* allocSingleton = [[Singleton alloc] init];
NSLog(@"%d", singleton == copySingleton);
NSLog(@"%d", singleton == mutableCopySingleton);
NSLog(@"%d", singleton == allocSingleton);
}
return 0;
}
运行结果如下:
可以看到创建的这几个实例对象都为同一个实例对象。
@synchronized的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改,保证代码的安全性。也就是包装这段代码是原子性的,安全的。这个是objective-c的一个锁定令牌,防止self对象在同一时间内被其他线程访问,起到保护线程安全的作用。
六、单例模式的几种常见写法
- 经典写法:
+(instancetype)mySingleton {
if (instance == nil) {
instance = [[self alloc] init];
}
return instance;
}
- 加锁写法:
+(instancetype)mySingleton {
if (instance == nil) {
@synchronized(self) {
if (instance == nil) {
instance = [[self alloc] init];
}
}
}
return instance;
}
- GCD写法
+(instancetype)mySingleton {
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 不能再使用 alloc 方法
// 因为已经重写了 allocWithZone 方法,所以这里要调用父类的分配空间的方法
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
dispatch_once 主要是根据 onceToken 的值来决定怎么去执行代码。
1.当 onceToken = 0 时,线程执行 dispatch_once 的 block 中代码;
2.当 onceToken = -1 时,线程跳过 dispatch_once 的 block 中代码不执行;
3.当 onceToken 为其他值时,线程被阻塞,等待 onceToken 值改变。
当线程调用mySingleton方法时,此时 onceToken = 0,调用 block 中的代码,此时 onceToken =其他值。
当其他线程再调用 mySingleton 方法时,onceToken为其他值,线程阻塞。当 block 线程执行完 block之后,onceToken = -1,其他线程不再阻塞,跳过 block。下次再调用mySingleton方法 时, block 已经为-1,直接跳过 block。