單例介紹
1.什么是單例
說到單例首先要提到單例模式,因?yàn)閱卫J绞菃卫嬖诘哪康?/p>
單例模式是一種常用的軟件設(shè)計(jì)模式爽哎。在它的核心結(jié)構(gòu)中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統(tǒng)中一個類只有一個實(shí)例而且該實(shí)例易于外界訪問吏垮,從而方便對實(shí)例個數(shù)的控制并節(jié)約系統(tǒng)資源。如果希望在系統(tǒng)中某個類的對象只能存在一個罐旗,單例模式是最好的解決方案膳汪。
單例,顧名思義:單獨(dú)的實(shí)例九秀。
簡單的說旅敷,單例是一個特殊的實(shí)例,在單例所屬的類中只存在單例這么一個實(shí)例颤霎,并且單例類似全局變量媳谁,在系統(tǒng)任意地方都能訪問單例
2.單例用處
根據(jù)單例模式的定義,我們知道一般兩種情況下使用單例:
系統(tǒng)中某種對象只能存在一個友酱,多了就會出問題
系統(tǒng)中某種對象實(shí)例只需要一個就夠用了晴音,多了占內(nèi)存
對于第一種情況,我們必須使用單例缔杉,對于第二種情況锤躁,我們雖然可以不用單例,但是單例是更優(yōu)的選擇
iOS的系統(tǒng)中有很多地方用的都是單例,例如
[UIApplication sharedApplication];
[NSNotificationCenter defaultCenter];[NSFileManager defaultManager];
[NSUserDefaults standardUserDefaults];[NSURLCache sharedURLCache];[NSHTTPCookieStorage sharedHTTPCookieStorage];
iOS單例的創(chuàng)建
1.單線程單例
我們知道對于單例類或详,我們必須留出一個接口來返回生成的單例系羞,由于一個類中只能有一個實(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讓其返回空夹供,不過這樣做也可能會讓使用接口的人誤解灵份,造成其他問題。所以我們一般情況下對alloc不做特殊處理哮洽。系統(tǒng)的單例也未對alloc做任何處理
2.@synchronized單例
對于一個實(shí)例填渠,我們一般并不能保證他一定會在單線程模式下使用,所以我們得適配多線程情況鸟辅。在多線程情況下揭蜒,上面的單例創(chuàng)建方式可能會出現(xiàn)問題。如果兩個線程同時調(diào)用shareInstance,可能會創(chuàng)建出2個single來剔桨。所以對于多線程情況下,我們需要使用@synchronized來加鎖徙融。
@implementationSingleton
+ (instancetype)shareInstance{
staticSingleton* single;
@synchronized(self){
if(!single) {
single = [[Singleton alloc] init];
}
}
return single;
}
@end
這樣的話洒缀,當(dāng)多個線程同時調(diào)用shareInstance時,由于@synchronized已經(jīng)加鎖欺冀,所以只能有一個線程進(jìn)入創(chuàng)建single树绩。這樣就解決了多線程下調(diào)用單例的問題
3.dispatch_once單例
使用@synchronized雖然解決了多線程的問題,但是并不完美隐轩。因?yàn)橹挥性趕ingle未創(chuàng)建時饺饭,我們加鎖才是有必要的。如果single已經(jīng)創(chuàng)建.這時候鎖不僅沒有好處职车,而且還會影響到程序執(zhí)行的性能(多個線程執(zhí)行@synchronized中的代碼時瘫俊,只有一個線程執(zhí)行,其他線程需要等待)悴灵。那么有沒有方法既可以解決問題扛芽,又不影響性能呢?
這個方法就是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時,線程執(zhí)行dispatch_once的block中代碼
當(dāng)onceToken= -1時茫孔,線程跳過dispatch_once的block中代碼不執(zhí)行
當(dāng)onceToken為其他值時叮喳,線程被線程被阻塞被芳,等待onceToken值改變
當(dāng)線程首先調(diào)用shareInstance,某一線程要執(zhí)行block中的代碼時馍悟,首先需要改變onceToken的值畔濒,再去執(zhí)行block中的代碼。這里onceToken的值變?yōu)榱?40734605830464赋朦。
這樣當(dāng)其他線程再獲取onceToken的值時篓冲,值已經(jīng)變?yōu)?40734605830464。其他線程被阻塞宠哄。
當(dāng)block線程執(zhí)行完block之后壹将。onceToken變?yōu)?1。其他線程不再阻塞毛嫉,跳過block诽俯。
下次再調(diào)用shareInstance時,block已經(jīng)為-1承粤。直接跳過block暴区。
這樣dispatch_once在首次調(diào)用時同步阻塞線程,生成單例之后辛臊,不再阻塞線程仙粱。dispatch_once是創(chuàng)建單例的最優(yōu)方案
總結(jié):
單例模式是一個很好的設(shè)計(jì)模式,他就像一個全局變量一樣彻舰,可以讓我們在任何地方都使用同一個實(shí)例伐割。
如果要自己創(chuàng)建單例模式,最好使用dispatch_once方法刃唤,這樣即可解決多線程問題隔心,又能達(dá)到高效的目的