美文网首页首页投稿(暂停使用,暂停投稿)
Objective - C 单例易被忽略的问题

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

作者: JiandanDream | 来源:发表于2017-12-02 19:57 被阅读35次

    本文主要谈使用 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],此时的 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 <Cat: 0x10050f0b0>
    2017-11-29 21:07:20.057139+0800 TestCommand[32407:3600761] dog <Dog: 0x100448740>
    

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

    后记

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

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

    参考资料:
    Apple Developer dispatch_once

    相关文章

      网友评论

        本文标题:Objective - C 单例易被忽略的问题

        本文链接:https://www.haomeiwen.com/subject/fqznbxtx.html