iOS-正確單例的寫法

前言

單例應(yīng)該是iOS中很簡單的設(shè)計模式,寫個單例很簡單很方便。網(wǎng)上例子也很多,大家也是基本上copy下來就可以了,但是要知其所以然這個問題的文章就很少。所以我在這寫一下好的單例,以及為什么這樣么寫。

創(chuàng)建單例的幾種方式

一、單線程模式單例

    + (instancetype)sharedInsance{
        static Singleton *singleton = nil;
        if (!singleton ) {
            singleton = [[Singleton alloc] init];
        }
        return singleton;
    }
  • 單線程單例只有在單個線程使用的情況下實用,在多線程的情況下,會產(chǎn)生線程不安全的情況。
  • 我們還需要把a(bǔ)lloc方法變?yōu)樗接蟹椒ú判?,?yán)格的單例是不允許再創(chuàng)建其他實例的,而alloc方法可以在外部任意生成實例
  • 兩條線程里同時調(diào)用sharedLoadData方法,沒有鎖的話可能會產(chǎn)生兩個singleton實例,這樣單例就失去意義了。

二、多線程加鎖單例

+ (instancetype)sharedInsance {
    static Singleton *singleton;
    @synchronized (self) {
        if (!singleton) {
            singleton = [[Singleton alloc] init];
        }
    }
    return singleton;
}
  • 加鎖以后,當(dāng)多個線程同時調(diào)用shareInstance時,由于@synchronized已經(jīng)加鎖,只能有一個線程創(chuàng)建singleton實例。
  • 只有在singleton未創(chuàng)建時,加鎖才是必要的。如果singleton已經(jīng)創(chuàng)建,這個時候還加鎖的話,會影響性能

三、多線程加鎖單例

    + (instancetype)sharedLoadData {
        static Singleton *singleton = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            singleton = [[Singleton alloc] init];
        });
    return singleton;
    }
  • dispatch_once 無論使用多線程還是單線程,都只執(zhí)行一次
  • GCD創(chuàng)建單例不僅可以解決多條線程的線程安全問題,也能保證性能,是官方推薦的方式。
  • 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值改變

dispatch_once執(zhí)行的流程

  • 當(dāng)線程調(diào)用shareInstance,此時onceToken = 0,調(diào)用block中的代碼。 此時onceToken的值變?yōu)?40734537148864。
  • 當(dāng)其他線程再調(diào)用shareInstance方法時,onceToken的值已經(jīng)是140734537148864了,線程阻塞。
  • 當(dāng)block線程執(zhí)行完block之后,onceToken變?yōu)?1.其他線程不再阻塞,跳過block。
  • 下次再調(diào)用shareInstance時,block已經(jīng)為-1.直接跳過block。

單例的健壯性

要是自己用的話,直接用shareInstance方法創(chuàng)建沒啥問題,但是如果同組或者別人沒注意用alloc創(chuàng)建、或者別人不小心使用copy、mutableCopy就可能產(chǎn)生兩個實例,也就不存在單例。健壯性就是要保持怎么創(chuàng)建就這個實力,就返回位子的內(nèi)存地址。

static Singleton* _instance = nil;
+ (instancetype)shareInstance {
    static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
        _instance = [[super allocWithZone:NULL] init] ;
        //不是使用alloc方法,而是調(diào)用[[super allocWithZone:NULL] init] 
        //已經(jīng)重載allocWithZone基本的對象分配方法,所以要借用父類(NSObject)的功能來幫助出處理底層內(nèi)存分配的雜物
    }) ;
    return _instance ;
    }
 
 //用alloc返回也是唯一實例
+(id) allocWithZone:(struct _NSZone *)zone {
    return [Singleton shareInstance] ;
}
//對對象使用copy也是返回唯一實例
-(id)copyWithZone:(NSZone *)zone {
    return [Singleton shareInstance] ;//return _instance;
}
 //對對象使用mutablecopy也是返回唯一實例
-(id)mutableCopyWithZone:(NSZone *)zone {
    return [Singleton shareInstance] ;
}
@end    

上面代碼注意點(diǎn):

  • 當(dāng)static關(guān)鍵字修飾局部變量時,只會初始化一次且在程序中只有一份內(nèi)存
  • copyWithZone mutablecopyWithZone 這個類遵守<NSCopying,NSMutableCopying>協(xié)議
  • 如果_instance = [self alloc] init];創(chuàng)建的話,將會和-(id) allocWithZone:(struct _NSZone *)zone產(chǎn)生死鎖。 dispatch_once中的onceToken線程被阻塞,等待onceToken值改變。
  • 當(dāng)用alloc創(chuàng)建對象、以及對對象進(jìn)行copy mutableCopy也是返回唯一實例

結(jié)尾

寫一個健壯的單例,知其所以然,才能更好的理解的單例。大家加油??!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 經(jīng)常用到單例,但也僅僅是停留在會用的層次,至于為什么這么用,內(nèi)部怎么實現(xiàn)的,從未研究過。在面試的時候,被問到了單例...
    YSL一路行走閱讀 2,664評論 5 29
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,632評論 1 32
  • 概要 單例模式是常見的設(shè)計模式。它的核心結(jié)構(gòu)中只包含一個被稱為單例類的特殊類。 通過單例模式可以保證系統(tǒng)中單例類只...
    NapoleonY閱讀 320評論 0 1
  • 單例介紹 本文源碼下載地址 1.什么是單例 說到單例首先要提到單例模式,因為單例模式是單例存在的目的 單例模式是一...
    雷鳴1010閱讀 3,678評論 0 19
  • coding 的演示功能不讓用,原來搭建的博客訪問不了了。索性將全部博客遷移到簡書,這篇是舊文章,歡迎大家以后來簡...
    小笨狼閱讀 890評論 0 14

友情鏈接更多精彩內(nèi)容