Objective - C 单例易被忽略的问题

本文主要谈使用 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 
2017-11-29 20:47:48.647050+0800 TestCommand[32243:3572489] dog 

不知道读者注意到没有,打印出来的,是同一个 Cat 实例,即使调用 [Dog sharedInstance],也是打印出了dog

再将打印的2行代码,颠倒一下顺序后运行:

NSLog(@"dog %@", [Dog sharedInstance]);
NSLog(@"cat %@", [Cat sharedInstance]);

结果:

2017-11-29 21:14:00.708044+0800 TestCommand[32543:3611288] dog 
2017-11-29 21:14:00.708392+0800 TestCommand[32543:3611288] cat 

打印出来的,由同一个 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],此时的 selfCat,返回 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 
2017-11-29 21:07:20.057139+0800 TestCommand[32407:3600761] dog 

其实,上面所说的原因,只要在 Animal 的 sharedInstance 中加个断点,就能验证。

后记

笔者遇到这个问题,初衷是想偷懒,想在父类声明并实现单例方法,这样,子类就可以不用重复写,实际使用过程中,因为项目复杂,出现了各种匪夷所思的现象,而这一切都源于对 dispatch_once 执行1次的理解不到位。

以上为个人见解,有写得不对的,欢迎指出,不胜感激!

参考资料:
Apple Developer dispatch_once

你可能感兴趣的:(Objective - C 单例易被忽略的问题)