iOS單例對象

1:概念

單例是一種常見的設計模式。經過單例模式能夠保證系統中一個類只有一個實例並且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。設計模式

在單例所屬的類中只存在這麼一個實例,而且相似全局變量,在系統任意位置都能訪問單例對象。多線程

2:用處

1)在系統中某種對象只能存在一個,多了不行。性能

2)系統中某種對象實例只須要一個就夠了,節省內存。ui

3:iOS系統中一些用到單例的地方

[UIApplication sharedApplication];
[NSNotificationCenter defaultCenter];
[NSFileManager defaultManager];
[NSUserDefaults standardUserDefaults];
[NSURLCache sharedURLCache];
[NSHTTPCookieStorage sharedHTTPCookieStorage];複製代碼

iOS單例的建立

1.單線程單例

咱們知道對於單例類,咱們必須留出一個接口來返回生成的單例,因爲一個類中只能有一個實例,因此咱們在第一次訪問這個實例的時候建立,以後訪問直接取已經建立好的實例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作任何處理線程

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。這樣就解決了多線程下調用單例的問題code

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方法,這樣便可解決多線程問題,又能達到高效的目的

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

相關文章
相關標籤/搜索