單例介紹
1.什么是單例
說到單例首先要提到單例模式,因?yàn)閱卫J绞菃卫嬖诘哪康?/p>
單例模式是一種常用的軟件設(shè)計(jì)模式。在它的核心結(jié)構(gòu)中只包含一個(gè)被稱為單例類的特殊類。通過單例模式可以保證系統(tǒng)中一個(gè)類只有一個(gè)實(shí)例而且該實(shí)例易于外界訪問,從而方便對(duì)實(shí)例個(gè)數(shù)的控制并節(jié)約系統(tǒng)資源。如果希望在系統(tǒng)中某個(gè)類的對(duì)象只能存在一個(gè),單例模式是最好的解決方案。
單例,顧名思義:單獨(dú)的實(shí)例。
簡單的說,單例是一個(gè)特殊的實(shí)例,在單例所屬的類中只存在單例這么一個(gè)實(shí)例,并且單例類似全局變量,在系統(tǒng)任意地方都能訪問單例
2.單例用處
根據(jù)單例模式的定義,我們知道一般兩種情況下使用單例:
系統(tǒng)中某種對(duì)象只能存在一個(gè),多了就會(huì)出問題
系統(tǒng)中某種對(duì)象實(shí)例只需要一個(gè)就夠用了,多了占內(nèi)存
對(duì)于第一種情況,我們必須使用單例,對(duì)于第二種情況,我們雖然可以不用單例,但是單例是更優(yōu)的選擇
iOS的系統(tǒng)中有很多地方用的都是單例,例如
[UIApplication sharedApplication];
[NSNotificationCenter defaultCenter];[NSFileManager defaultManager];
[NSUserDefaults standardUserDefaults];[NSURLCache sharedURLCache];[NSHTTPCookieStorage sharedHTTPCookieStorage];
iOS單例的創(chuàng)建
1.單線程單例
我們知道對(duì)于單例類,我們必須留出一個(gè)接口來返回生成的單例,由于一個(gè)類中只能有一個(gè)實(shí)例,所以我們?cè)诘谝淮卧L問這個(gè)實(shí)例的時(shí)候創(chuàng)建,之后訪問直接取已經(jīng)創(chuàng)建好的實(shí)例
@implementationSingleton
+ (instancetype)shareInstance{
staticSingleton* single;
if(!single) {
single = [[Singleton alloc] init];
}
return single;
}
@end
ps:嚴(yán)格意義上來說,我們還需要將alloc方法封住,因?yàn)閲?yán)格的單例是不允許再創(chuàng)建其他實(shí)例的,而alloc方法可以在外部任意生成實(shí)例。但是考慮到alloc屬于NSObject,iOS中無法將alloc變成私有方法,最多只能覆蓋alloc讓其返回空,不過這樣做也可能會(huì)讓使用接口的人誤解,造成其他問題。所以我們一般情況下對(duì)alloc不做特殊處理。系統(tǒng)的單例也未對(duì)alloc做任何處理
2.@synchronized單例
對(duì)于一個(gè)實(shí)例,我們一般并不能保證他一定會(huì)在單線程模式下使用,所以我們得適配多線程情況。在多線程情況下,上面的單例創(chuàng)建方式可能會(huì)出現(xiàn)問題。如果兩個(gè)線程同時(shí)調(diào)用shareInstance,可能會(huì)創(chuàng)建出2個(gè)single來。所以對(duì)于多線程情況下,我們需要使用@synchronized來加鎖。
@implementationSingleton
+ (instancetype)shareInstance{
staticSingleton* single;
@synchronized(self){
if(!single) {
single = [[Singleton alloc] init];
}
}
return single;
}
@end
這樣的話,當(dāng)多個(gè)線程同時(shí)調(diào)用shareInstance時(shí),由于@synchronized已經(jīng)加鎖,所以只能有一個(gè)線程進(jìn)入創(chuàng)建single。這樣就解決了多線程下調(diào)用單例的問題
3.dispatch_once單例
使用@synchronized雖然解決了多線程的問題,但是并不完美。因?yàn)橹挥性趕ingle未創(chuàng)建時(shí),我們加鎖才是有必要的。如果single已經(jīng)創(chuàng)建.這時(shí)候鎖不僅沒有好處,而且還會(huì)影響到程序執(zhí)行的性能(多個(gè)線程執(zhí)行@synchronized中的代碼時(shí),只有一個(gè)線程執(zhí)行,其他線程需要等待)。那么有沒有方法既可以解決問題,又不影響性能呢?
這個(gè)方法就是GCD中的dispatch_once
@implementationSingleton
+ (instancetype)shareInstance{
static Singleton* single;
static dispatch_once_t onceToken;
//①onceToken = 0;
dispatch_once(&onceToken, ^{
NSLog(@"%ld",onceToken);
//②onceToken = 140734731430192
single = [[Singleton alloc] init];
});
NSLog(@"%ld",onceToken);
//③onceToken = -1;
return single;
}
}
@end
打印結(jié)果如下:
2016-12-19 17:39:28.484 11-一次性執(zhí)行[9619:621917] 140734605830464
2016-12-19 17:39:28.484 11-一次性執(zhí)行[9619:621917] -1
dispatch_once為什么能做到既解決同步多線程問題又不影響性能呢?
下面我們來看看dispatch_once的原理:
dispatch_once主要是根據(jù)onceToken的值來決定怎么去執(zhí)行代碼。
當(dāng)onceToken= 0時(shí),線程執(zhí)行dispatch_once的block中代碼
當(dāng)onceToken= -1時(shí),線程跳過dispatch_once的block中代碼不執(zhí)行
當(dāng)onceToken為其他值時(shí),線程被線程被阻塞,等待onceToken值改變
當(dāng)線程首先調(diào)用shareInstance,某一線程要執(zhí)行block中的代碼時(shí),首先需要改變onceToken的值,再去執(zhí)行block中的代碼。這里onceToken的值變?yōu)榱?40734605830464。
這樣當(dāng)其他線程再獲取onceToken的值時(shí),值已經(jīng)變?yōu)?40734605830464。其他線程被阻塞。
當(dāng)block線程執(zhí)行完block之后。onceToken變?yōu)?1。其他線程不再阻塞,跳過block。
下次再調(diào)用shareInstance時(shí),block已經(jīng)為-1。直接跳過block。
這樣dispatch_once在首次調(diào)用時(shí)同步阻塞線程,生成單例之后,不再阻塞線程。dispatch_once是創(chuàng)建單例的最優(yōu)方案
總結(jié):
單例模式是一個(gè)很好的設(shè)計(jì)模式,他就像一個(gè)全局變量一樣,可以讓我們?cè)谌魏蔚胤蕉际褂猛粋€(gè)實(shí)例。
如果要自己創(chuàng)建單例模式,最好使用dispatch_once方法,這樣即可解決多線程問題,又能達(dá)到高效的目的