单例,是我们在开发中使用非常多的一种设计模式。但是,我们真的有写好一个单例么?这篇文章记录了单例的一些常见问题。
- 常见的单例写法及存在的问题
- 好的单例怎么写
- 单例的特殊需求
普通写法
1 2 3 4 5 6 7 8 9 10 11
| @implementation SingletonClass
+ (instancetype)shareInstance { static SingletonClass *_sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedInstance = [[self alloc] init]; }); return _sharedInstance; } @end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // MARK : singleton - (void)testSingleton { SingletonClass *cls1 = [SingletonClass shareInstance]; NSLog(@"cls1 = %@", cls1); SingletonClass *cls2 = [SingletonClass shareInstance]; NSLog(@"cls2 = %@", cls2); SingletonClass *cls3 = [[SingletonClass alloc] init]; NSLog(@"cls3 = %@", cls3); SingletonClass *cls4 = [cls1 copy]; NSLog(@"cls4 = %@", cls4); }
|
1 2 3 4 5
| Test[4588:4307534] cls1 = <SingletonClass: 0x600002688150> Test[4588:4307534] cls2 = <SingletonClass: 0x600002688150> Test[4588:4307534] cls3 = <SingletonClass: 0x6000026a0260> Test[4588:4307534] -[SingletonClass copyWithZone:]: unrecognized selector sent to instance 0x600002688150
|
所以,当我们通过不同的途径获取对象,并不能保证对象的唯一性,,所以我们就需要封锁用户通过 alloc 和 init 以及 copy 来构造对象这条道路。
完善写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @implementation SingletonClass + (instancetype)shareInstance { static SingletonClass *_sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedInstance = [[super allocWithZone:NULL] init]; }); return _sharedInstance; }
+ (instancetype)allocWithZone:(struct _NSZone *)zone { return [self shareInstance]; }
- (id)copyWithZone:(nullable NSZone *)zone { return [SingletonClass shareInstance]; } @end
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| - (void)testSingleton { SingletonClass *cls1 = [SingletonClass shareInstance]; NSLog(@"cls1 = %@", cls1); SingletonClass *cls2 = [SingletonClass shareInstance]; NSLog(@"cls2 = %@", cls2); SingletonClass *cls3 = [[SingletonClass alloc] init]; NSLog(@"cls3 = %@", cls3); SingletonClass *cls4 = [cls1 copy]; NSLog(@"cls4 = %@", cls4); }
|
1 2 3 4
| Test[4793:4317590] cls1 = <SingletonClass: 0x600003e8c460> Test[4793:4317590] cls2 = <SingletonClass: 0x600003e8c460> Test[4793:4317590] cls3 = <SingletonClass: 0x600003e8c460> Test[4793:4317590] cls4 = <SingletonClass: 0x600003e8c460>
|
上方的几个问题这种写法都解决了。
特殊的需求
可继承的单例
因为单例具有唯一性,要保证每个创建的类是不同的,所以在每个类生成时,我们动态的给类绑定唯一的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #import <objc/runtime.h>
@implementation InheritableSharedInstanceClass
+ (instancetype)sharedInstance { id sharedInstance = objc_getAssociatedObject(self, @"SharedInstance"); if (!sharedInstance) { sharedInstance = [[super allocWithZone:NULL] init]; objc_setAssociatedObject(self, @"SharedInstance", sharedInstance, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return sharedInstance; }
+ (instancetype)allocWithZone:(struct _NSZone *)zone { return [self sharedInstance]; }
- (id)copyWithZone:(nullable NSZone *)zone { return [[self class] sharedInstance]; }
@end
|
1 2 3 4 5 6 7
| - (void)testSingleton { SingletonClass *cls = [SingletonClass shareInstance]; NSLog(@"cls = %@", cls); SubSingletonClass *subCls = [SubSingletonClass shareInstance]; NSLog(@"subCls = %@", subCls); }
|
1 2
| cls = <SingletonClass: 0x6000017ebb10> subCls = <SubSingletonClass: 0x6000017cc4d0>
|
单例的销毁
虽然单例销毁需要销毁的情况还是比较少的,这里还是加一下,防止特殊的需求出现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static dispatch_once_t onceToken; static SingletonClass *_sharedInstance = nil;
@implementation SingletonClass + (instancetype)shareInstance { dispatch_once(&onceToken, ^{ _sharedInstance = [[super allocWithZone:NULL] init]; }); return _sharedInstance; }
+ (instancetype)allocWithZone:(struct _NSZone *)zone { return [self shareInstance]; }
- (id)copyWithZone:(nullable NSZone *)zone { return [SingletonClass shareInstance]; }
+ (void)shouldDealloc { onceToken = 0; _sharedInstance = nil; } @end
|
1 2 3 4 5 6 7 8 9
| - (void)testSingleton { SingletonClass *clsOld = [SingletonClass shareInstance]; NSLog(@"clsOld = %@", clsOld); [SingletonClass shouldDealloc]; SingletonClass *clsNew = [SingletonClass shareInstance]; NSLog(@"clsNew = %@", clsNew); }
|
1 2
| clsOld = <SingletonClass: 0x600003e3d9b0> clsNew = <SingletonClass: 0x600003e3d9a0>
|
必须把 static dispatch_once_t onceToken; 这个拿到函数体外,成为全局的。
onceToken 置成 0,GCD 才会认为它从未执行过.它默认为 0,这样才能保证下次再次调用 shareInstance 的时候,再次创建对象。
1 2 3 4
| class SingletonClass { static let sharedInstance = SingletonClass() private init() {} // 初始化方法设置为私有,防止被直接使用初始化方法创建 }
|