本文主要谈使用 dispatch_once 实现单例时,遇到的一个极其容易被忽略的问题:
父类声明并实现单例方法,子类未覆写该方法,当调用子类单例方法时,返回的实例不一定是该子类的实例。
有点绕,下面具体解释一下:
单例的实现
使用 dispatch_once 实现单例,具体代码:
+ (instancetype)sharedInstance {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
问题例子
父类 Animal,声明并实现了 sharedInstance 这一单例方法,代码如下:
// .h
@interface Animal : NSObject
+ (instancetype)sharedInstance;
@end
// .m
@implementation Animal
+ (instancetype)sharedInstance {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
@end
有2个子类:Cat、Dog,只是简单继承 Animal,都没有覆写 sharedInstance 方法。
首先,打印2个子类:
NSLog(@"cat %@", [Cat sharedInstance]);
NSLog(@"dog %@", [Dog sharedInstance]);
结果:
2017-11-29 20:47:48.646649+0800 TestCommand[32243:3572489] cat <Cat: 0x100404020>
2017-11-29 20:47:48.647050+0800 TestCommand[32243:3572489] dog <Cat: 0x100404020>
不知道读者注意到没有,打印出来的,是同一个 Cat 实例,即使调用 [Dog sharedInstance],也是打印出了dog <Cat: 0x100404020>
再将打印的2行代码,颠倒一下顺序后运行:
NSLog(@"dog %@", [Dog sharedInstance]);
NSLog(@"cat %@", [Cat sharedInstance]);
结果:
2017-11-29 21:14:00.708044+0800 TestCommand[32543:3611288] dog <Dog: 0x10068c0b0>
2017-11-29 21:14:00.708392+0800 TestCommand[32543:3611288] cat <Dog: 0x10068c0b0>
打印出来的,由同一个 Cat 实例,变成了同一个 Dog 实例。
看起来有些诡异,似乎单例方法返回的实例,还跟调用顺序有关,而这就是一开始说的:父类声明并实现单例方法,子类未覆写该方法,当调用子类单例方法时,返回的实例不一定是该子类的实例
的现象:
[Cat sharedInstance] 返回的实例,并不一定是 Cat 实例。
原因
先看看关于 void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block) 的官方解释:
Executes a block object once and only once for the lifetime of an application.
This function is useful for initialization of global data (singletons) in an application.
官方资料提到:只执行1次,当然这是众所周知的,其实,这就是上面例子的原因。
结合上面代码,挼一下思路:
NSLog(@"cat %@", [Cat sharedInstance]);
NSLog(@"dog %@", [Dog sharedInstance]);
Cat 和 Dog 都没有覆写父类 Animal 的 SharedInstance 方法,这是前提。
当调用 [Cat sharedInstance] 时,根据继承的规则,其实是执行了父类 Animal 的 sharedInstance 方法,其中执行了 [[self alloc] init]
,此时的 self
是 Cat
,返回 Cat 实例。
当再调用 [Dog sharedInstance] 时,仍然是执行父类的方法,而因为 dispatch_once 的只执行1次,[[self alloc] init]
不能再被执行,只会再次返回 Cat 实例。
如果子类覆写了 sharedInstance,那么结果就会不一样。
比如 Cat 覆写了 sharedInstance,同样先打印 Cat 实例,结果如下:
2017-11-29 21:07:20.056802+0800 TestCommand[32407:3600761] cat <Cat: 0x10050f0b0>
2017-11-29 21:07:20.057139+0800 TestCommand[32407:3600761] dog <Dog: 0x100448740>
其实,上面所说的原因,只要在 Animal 的 sharedInstance 中加个断点,就能验证。
后记
笔者遇到这个问题,初衷是想偷懒,想在父类声明并实现单例方法,这样,子类就可以不用重复写,实际使用过程中,因为项目复杂,出现了各种匪夷所思的现象,而这一切都源于对 dispatch_once 执行1次的理解不到位。
以上为个人见解,有写得不对的,欢迎指出,不胜感激!
网友评论