單例是一種常見的設計模式。經過單例模式能夠保證系統中一個類只有一個實例並且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。設計模式
在單例所屬的類中只存在這麼一個實例,而且相似全局變量,在系統任意位置都能訪問單例對象。多線程
1)在系統中某種對象只能存在一個,多了不行。性能
2)系統中某種對象實例只須要一個就夠了,節省內存。ui
[UIApplication sharedApplication];
[NSNotificationCenter defaultCenter];
[NSFileManager defaultManager];
[NSUserDefaults standardUserDefaults];
[NSURLCache sharedURLCache];
[NSHTTPCookieStorage sharedHTTPCookieStorage];複製代碼
咱們知道對於單例類
,咱們必須留出一個接口來返回生成的單例,因爲一個類中只能有一個實例,因此咱們在第一次訪問這個實例的時候建立,以後訪問直接取已經建立好的實例spa
@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
作任何處理線程
對於一個實例,咱們通常並不能保證他必定會在單線程模式下使用,因此咱們得適配多線程狀況。在多線程狀況下,上面的單例建立方式可能會出現問題。若是兩個線程同時調用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
。這樣就解決了多線程下調用單例的問題code
使用@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
方法,這樣便可解決多線程問題,又能達到高效的目的單例雖然好用,不過他並不適合繼承和擴展,因此使用單例的時候要注意這點。千萬不要任何東西都使用單例,要適可而止