iOS單例物件

左忠飛發表於2019-02-27

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的值來決定怎麼去執行程式碼。

  1. onceToken = 0時,執行緒執行dispatch_onceblock中程式碼
  2. onceToken = -1時,執行緒跳過dispatch_onceblock中程式碼不執行
  3. onceToken為其他值時,執行緒被執行緒被阻塞,等待onceToken值改變

當執行緒首先呼叫shareInstance,某一執行緒要執行block中的程式碼時,首先需要改變onceToken的值,再去執行block中的程式碼。這裡onceToken的值變為了140734731430192。
這樣當其他執行緒再獲取onceToken的值時,值已經變為140734731430192。其他執行緒被阻塞。
block執行緒執行完block之後。onceToken變為-1。其他執行緒不再阻塞,跳過block
下次再呼叫shareInstance時,block已經為-1。直接跳過block
這樣dispatch_once在首次呼叫時同步阻塞執行緒,生成單例之後,不再阻塞執行緒。dispatch_once是建立單例的最優方案

總結:

  1. 單例模式是一個很好的設計模式,他就像一個全域性變數一樣,可以讓我們在任何地方都使用同一個例項。
  2. 如果要自己建立單例模式,最好使用dispatch_once方法,這樣即可解決多執行緒問題,又能達到高效的目的

單例雖然好用,不過他並不適合繼承和擴充套件,所以使用單例的時候要注意這點。千萬不要任何東西都使用單例,要適可而止


相關文章