Objective - C 單例易被忽略的問(wèn)題

本文主要談使用 dispatch_once 實(shí)現(xiàn)單例時(shí),遇到的一個(gè)極其容易被忽略的問(wèn)題:
父類(lèi)聲明并實(shí)現(xiàn)單例方法,子類(lèi)未覆寫(xiě)該方法,當(dāng)調(diào)用子類(lèi)單例方法時(shí),返回的實(shí)例不一定是該子類(lèi)的實(shí)例。
有點(diǎn)繞,下面具體解釋一下:

單例的實(shí)現(xiàn)

使用 dispatch_once 實(shí)現(xiàn)單例,具體代碼:

+ (instancetype)sharedInstance {
    static id instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

問(wèn)題例子

父類(lèi) Animal,聲明并實(shí)現(xiàn)了 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個(gè)子類(lèi):Cat、Dog,只是簡(jiǎn)單繼承 Animal,都沒(méi)有覆寫(xiě) sharedInstance 方法。

首先,打印2個(gè)子類(lèi):

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

結(jié)果:

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>

不知道讀者注意到?jīng)]有,打印出來(lái)的,是同一個(gè) Cat 實(shí)例,即使調(diào)用 [Dog sharedInstance],也是打印出了dog <Cat: 0x100404020>

再將打印的2行代碼,顛倒一下順序后運(yùn)行:

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

結(jié)果:

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>

打印出來(lái)的,由同一個(gè) Cat 實(shí)例,變成了同一個(gè) Dog 實(shí)例。

看起來(lái)有些詭異,似乎單例方法返回的實(shí)例,還跟調(diào)用順序有關(guān),而這就是一開(kāi)始說(shuō)的:父類(lèi)聲明并實(shí)現(xiàn)單例方法,子類(lèi)未覆寫(xiě)該方法,當(dāng)調(diào)用子類(lèi)單例方法時(shí),返回的實(shí)例不一定是該子類(lèi)的實(shí)例
的現(xiàn)象:
[Cat sharedInstance] 返回的實(shí)例,并不一定是 Cat 實(shí)例。

原因

先看看關(guān)于 void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block) 的官方解釋?zhuān)?/p>

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.

官方資料提到:只執(zhí)行1次,當(dāng)然這是眾所周知的,其實(shí),這就是上面例子的原因。
結(jié)合上面代碼,挼一下思路:

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

Cat 和 Dog 都沒(méi)有覆寫(xiě)父類(lèi) Animal 的 SharedInstance 方法,這是前提。
當(dāng)調(diào)用 [Cat sharedInstance] 時(shí),根據(jù)繼承的規(guī)則,其實(shí)是執(zhí)行了父類(lèi) Animal 的 sharedInstance 方法,其中執(zhí)行了 [[self alloc] init],此時(shí)的 selfCat,返回 Cat 實(shí)例。
當(dāng)再調(diào)用 [Dog sharedInstance] 時(shí),仍然是執(zhí)行父類(lèi)的方法,而因?yàn)?dispatch_once 的只執(zhí)行1次,[[self alloc] init] 不能再被執(zhí)行,只會(huì)再次返回 Cat 實(shí)例。

如果子類(lèi)覆寫(xiě)了 sharedInstance,那么結(jié)果就會(huì)不一樣。
比如 Cat 覆寫(xiě)了 sharedInstance,同樣先打印 Cat 實(shí)例,結(jié)果如下:

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>

其實(shí),上面所說(shuō)的原因,只要在 Animal 的 sharedInstance 中加個(gè)斷點(diǎn),就能驗(yàn)證。

后記

筆者遇到這個(gè)問(wèn)題,初衷是想偷懶,想在父類(lèi)聲明并實(shí)現(xiàn)單例方法,這樣,子類(lèi)就可以不用重復(fù)寫(xiě),實(shí)際使用過(guò)程中,因?yàn)轫?xiàng)目復(fù)雜,出現(xiàn)了各種匪夷所思的現(xiàn)象,而這一切都源于對(duì) dispatch_once 執(zhí)行1次的理解不到位。

以上為個(gè)人見(jiàn)解,有寫(xiě)得不對(duì)的,歡迎指出,不勝感激!

參考資料:
Apple Developer dispatch_once

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 2017年11月4日 星期六 晴 二年級(jí)五班代路瑤爸爸 親子日記第三十四篇 今天是周六,吃過(guò)早飯,看...
    人弋三壽閱讀 138評(píng)論 0 0
  • 麗斯百合 是一家牛排店的名字 搞不清為什么不加愛(ài)麗絲,為什么不加莉莉,就像我搞不清你為什么要叫,艾瑞絲 這家店,如...
    我說(shuō)我叫金閱讀 374評(píng)論 0 0
  • 每個(gè)大人都曾經(jīng)是個(gè)孩子,雖然大人很少記得這一點(diǎn),但每個(gè)大人心中確實(shí)都曾住著一個(gè)小王子 。 作為一個(gè)永恒的悲觀主義者...
    阿通Midori閱讀 1,533評(píng)論 2 7
  • 旅途無(wú)聊,開(kāi)喜馬拉雅聽(tīng)了會(huì)兒《朗讀者》。這個(gè)節(jié)目很奇怪,坐在電視機(jī)前看時(shí),我?guī)缀鹾茈y體會(huì)到嘉賓口中文字的重量,而有...
    遲鈍的生活觀察者閱讀 399評(píng)論 0 2