iOS 单例的创建
1. 单线程单例
我们知道对于单例类, 我们必须留出一个接口来返回生成的单例, 由于一个类中只能有一个实例, 所以我们在第一次访问这个实例的时候创建, 之后访问直接取已经创建好的实例
- @implementation Singleton
- + (instancetype)shareInstance
- {
- static Singleton* single;
- if (!single) {
- single = [[Singleton alloc] init];
- }
- return single;
- }
- @end
ps: 严格意义上来说, 我们还需要将 alloc 方法封住, 因为严格的单例是不允许再创建其他实例的, 而 alloc 方法可以在外部任意生成实例. 但是考虑到 alloc 属于 NSObject,iOS 中无法将 alloc 变成私有方法, 最多只能覆盖 alloc 让其返回空, 不过这样做也可能会让使用接口的人误解, 造成其他问题. 所以我们一般情况下对 alloc 不做特殊处理. 系统的单例也未对 alloc 做任何处理
2.@synchronized 单例
对于一个实例, 我们一般并不能保证他一定会在单线程模式下使用, 所以我们得适配多线程情况. 在多线程情况下, 上面的单例创建方式可能会出现问题. 如果两个线程同时调用 shareInstance, 可能会创建出 2 个 single 来. 所以对于多线程情况下, 我们需要使用 @synchronized 来加锁.
- @implementation Singleton
- + (instancetype)shareInstance
- {
- static Singleton* single;
- @synchronized(self){
- if (!single) {
- single = [[Singleton alloc] init];
- }
- }
- return single;
- }
- @end
这样的话, 当多个线程同时调用 shareInstance 时, 由于 @synchronized 已经加锁, 所以只能有一个线程进入创建 single. 这样就解决了多线程下调用单例的问题
3.dispatch_once 单例
使用 @synchronized 虽然解决了多线程的问题, 但是并不完美. 因为只有在 single 未创建时, 我们加锁才是有必要的. 如果 single 已经创建. 这时候锁不仅没有好处, 而且还会影响到程序执行的性能 (多个线程执行 @synchronized 中的代码时, 只有一个线程执行, 其他线程需要等待). 那么有没有方法既可以解决问题, 又不影响性能呢?
这个方法就是 GCD 中的 dispatch_once
- @implementation Singleton
- + (instancetype)shareInstance
- {
- static Singleton* single;
- static dispatch_once_t onceToken; //1onceToken = 0;
- dispatch_once(&onceToken, ^{
- NSLog(@"%ld",onceToken); //2onceToken = 140734731430192
- single = [[Singleton alloc] init];
- });
- NSLog(@"%ld",onceToken); //3onceToken = -1;
- return single;
- }
- @end
dispatch_once 为什么能做到既解决同步多线程问题又不影响性能呢?
下面我们来看看 dispatch_once 的原理:
dispatch_once 主要是根据 onceToken 的值来决定怎么去执行代码.
当 onceToken = 0 时, 线程执行 dispatch_once 的 block 中代码
当 onceToken = -1 时, 线程跳过 dispatch_once 的 block 中代码不执行
当 onceToken 为其他值时, 线程被线程被阻塞, 等待 onceToken 值改变
当线程首先调用 shareInstance, 某一线程要执行 block 中的代码时, 首先需要改变 onceToken 的值, 再去执行 block 中的代码. 这里 onceToken 的值变为了 140734731430192.
这样当其他线程再获取 onceToken 的值时, 值已经变为 140734731430192. 其他线程被阻塞.
当 block 线程执行完 block 之后. onceToken 变为 - 1. 其他线程不再阻塞, 跳过 block.
下次再调用 shareInstance 时, block 已经为 - 1. 直接跳过 block.
这样 dispatch_once 在首次调用时同步阻塞线程, 生成单例之后, 不再阻塞线程. dispatch_once 是创建单例的最优方案
总结:
单例模式是一个很好的设计模式, 他就像一个全局变量一样, 可以让我们在任何地方都使用同一个实例.
如果要自己创建单例模式, 最好使用 dispatch_once 方法, 这样即可解决多线程问题, 又能达到高效的目的
单例虽然好用, 不过他并不适合继承和扩展, 所以使用单例的时候要注意这点. 千万不要任何东西都使用单例, 要适可而止
来源: https://juejin.im/post/5c765d91518825406d2954ca