本文主要談使用 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í)的 self
是 Cat
,返回 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ì)的,歡迎指出,不勝感激!