1:概念
單例是一種常見的設計模式。通過單例模式可以保證系統中一個類只有一個例項而且該例項易於外界訪問,從而方便對例項個數的控制並節約系統資源。
在單例所屬的類中只存在這麼一個例項,並且類似全域性變數,在系統任意位置都能訪問單例物件。
2:用處
1)在系統中某種物件只能存在一個,多了不行。
2)系統中某種物件例項只需要一個就夠了,節省記憶體。
3:iOS系統中一些用到單例的地方
[UIApplication sharedApplication];
[NSNotificationCenter defaultCenter];
[NSFileManager defaultManager];
[NSUserDefaults standardUserDefaults];
[NSURLCache sharedURLCache];
[NSHTTPCookieStorage sharedHTTPCookieStorage];複製程式碼
iOS單例的建立
1.單執行緒單例
我們知道對於單例類
,我們必須留出一個介面來返回生成的單例,由於一個類中只能有一個例項,所以我們在第一次訪問這個例項的時候建立,之後訪問直接取已經建立好的例項
@implementation Singleton
+ (instancetype)shareInstance
{
static Singleton* single;
if (!single) {
single = [[Singleton alloc] init];
}
return single;
}
@end
複製程式碼
ps:嚴格意義上來說,我們還需要將
alloc
方法封住,因為嚴格的單例是不允許再建立其他例項的,而alloc
方法可以在外部任意生成例項。但是考慮到alloc
屬於NSObject,iOS中無法將alloc
變成私有方法,最多隻能覆蓋alloc
讓其返回空,不過這樣做也可能會讓使用介面的人誤解,造成其他問題。所以我們一般情況下對alloc
不做特殊處理。系統的單例也未對alloc
做任何處理
2.@synchronized單例
對於一個例項,我們一般並不能保證他一定會在單執行緒模式下使用,所以我們得適配多執行緒情況。在多執行緒情況下,上面的單例建立方式可能會出現問題。如果兩個執行緒同時呼叫shareInstance
,可能會建立出2個single來。所以對於多執行緒情況下,我們需要使用@synchronized
來加鎖。
@implementation Singleton
+ (instancetype)shareInstance
{
static Singleton* single;
@synchronized(self){
if (!single) {
single = [[Singleton alloc] init];
}
}
return single;
}
@end
複製程式碼
這樣的話,當多個執行緒同時呼叫shareInstance
時,由於@synchronized
已經加鎖,所以只能有一個執行緒進入建立single
。這樣就解決了多執行緒下呼叫單例的問題
3.dispatch_once單例
使用@synchronized
雖然解決了多執行緒的問題,但是並不完美。因為只有在single
未建立時,我們加鎖才是有必要的。如果single
已經建立.這時候鎖不僅沒有好處,而且還會影響到程式執行的效能(多個執行緒執行@synchronized
中的程式碼時,只有一個執行緒執行,其他執行緒需要等待)。那麼有沒有方法既可以解決問題,又不影響效能呢?
這個方法就是GCD中的dispatch_once
@implementation Singleton
+ (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
複製程式碼
dispatch_once
為什麼能做到既解決同步多執行緒問題又不影響效能呢?
下面我們來看看dispatch_once
的原理:
dispatch_once
主要是根據onceToken
的值來決定怎麼去執行程式碼。
- 當
onceToken
= 0時,執行緒執行dispatch_once
的block
中程式碼 - 當
onceToken
= -1時,執行緒跳過dispatch_once
的block
中程式碼不執行 - 當
onceToken
為其他值時,執行緒被執行緒被阻塞,等待onceToken
值改變
當執行緒首先呼叫shareInstance
,某一執行緒要執行block
中的程式碼時,首先需要改變onceToken
的值,再去執行block中的程式碼。這裡onceToken
的值變為了140734731430192。
這樣當其他執行緒再獲取onceToken
的值時,值已經變為140734731430192。其他執行緒被阻塞。
當block
執行緒執行完block
之後。onceToken
變為-1。其他執行緒不再阻塞,跳過block
。
下次再呼叫shareInstance
時,block已經為-1。直接跳過block
。
這樣dispatch_once
在首次呼叫時同步阻塞執行緒,生成單例之後,不再阻塞執行緒。dispatch_once
是建立單例的最優方案
總結:
- 單例模式是一個很好的設計模式,他就像一個全域性變數一樣,可以讓我們在任何地方都使用同一個例項。
- 如果要自己建立單例模式,最好使用
dispatch_once
方法,這樣即可解決多執行緒問題,又能達到高效的目的
單例雖然好用,不過他並不適合繼承和擴充套件,所以使用單例的時候要注意這點。千萬不要任何東西都使用單例,要適可而止