单例模式重新学习
文章目录
- 从头开始构建单例
- 饿汉模式
- 总结
- P.S:Code Snippets:自定义代码片段
在开发项目中,有很多时候我们需要一个全局的对象,而且要保证全局有且仅有一份即可,单例在这个时候就是最佳的选择,系统已经有很多单例存在,例如UIApplication、NSNotification、NSFileManager等等
从头开始构建单例
我们先来看一个最简单的单例模式,首先我们要有一个能够在全局获取到单例的类方法
+ (id)sharedInstance { static Singleton *instance = nil;if (!instance) { instance = [[super allocWithZone:NULL] init];} return instance; }
但其实如果按照这个写法,在遇到多线程时,是有可能多次初始化单例
所以我们使用dispatch_once
保证线程安全,dispatch_once
是原子性的
+ (instancetype)sharedInstance {static Singleton *instance = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [[super allocWithZone:NULL] init];});return instance;
}
接下来实现类本身的方法
一个自定义类,应该需要申请内存(alloc)和初始化(init)两步,
我们要确保对象的唯一性,因此在第一步这个阶段我们就要拦截它。当我们调用alloc方法时,OC内部会调用allocWithZone这个方法来申请内存,我们重写这个方法,然后在这个方法中调用shareInstance方法返回单例对象,这样就可以达到我们的目的
除了申请内存和初始化,拷贝也有可能会导致单例出现多次,所以重写copyWithZone方法,然后在这个方法中调用shareInstance方法返回单例对象
+ (instancetype)sharedInstance {static Singleton *instance = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [[super allocWithZone:NULL] init];});return instance;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone {return [self sharedInstance];
}- (id)copyWithZone:(NSZone *)zone {return self;
}- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}
这样,一个基本的单例方法,就全部实现了,基本上可以避免出现多个实例或者奇奇怪怪的情况出现
这种方式叫做懒汉模式,这种模式创建出的单例只会在我们需要的时候创建,这种模式可以节省系统资源,提高性能
饿汉模式
饿汉模式与懒汉模式最大的不同就是他们会在一开始就把示例创建好,一般来说,会在load中创建示例来实现
+load
方法是当类或者分类被加载到内存中时,系统自动调用的,该方法只会调用一次,所有的类都会在程序启动的时候加载一次
// 在类的实现文件中声明一个静态实例
static Singleton *instance = nil;@implementation Singleton// 在 +load 方法中创建单例实例
+ (void)load {instance = [[super allocWithZone:NULL] init];
}// 饿汉模式的全局访问点
+ (instancetype)sharedInstance {return instance;
}// 重写 allocWithZone: 方法,确保无法通过 alloc 直接创建新实例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {// 直接返回已经创建好的单例实例return [self sharedInstance];
}// 重写 copy 和 mutableCopy 方法,防止实例被复制
- (id)copyWithZone:(NSZone *)zone {return self;
}- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}@end
由于load方法只会调用一次,并且是系统调用,所以这里绝对是线程安全的,我们也就不需要加入dispatch_once
来保证线程安全的问题了
总结
- 饿汉模式:
- 创建时机:应用启动时
- 优点:简单、线程安全、调用快
- 适用场景:单例对象不大,或者在应用生命周期中几乎总是会用到
- 懒汉模式:
- 创建时机:第一次使用时
- 优点:节省内存、减少启动耗时
- 适用场景:单例对象比较大,或者不确定是否会用到
在 iOS 开发中,懒汉模式(尤其是使用 dispatch_once
的方式)是最常用和推荐的单例实现方式。它结合了懒加载的优点,同时完美地解决了线程安全问题,是目前最优雅的解决方案
P.S:Code Snippets:自定义代码片段
Code Snippets是一个可以帮助你减少写重复代码的工具
对于单例这种可能会经常用的代码块,可以添加入自定义的代码片段
我们只需要:
- 选中代码
- 右键 → Create Code Snippet
- 设置快捷键,比如
singleton
以后输入singleton
就可以立即展开代码片段
之后的重复代码也都可以通过这个方式来添加进xcode