@sychronized和dispatch_once揍愁,以及對單例的思考

在iOS開發(fā)中呐萨,經(jīng)常使用到單例。單例是Cocoa中被廣泛使用的設(shè)計模式之一莽囤。單例使得某個類在整個application的生命周期中只有一個實(shí)例谬擦,減少內(nèi)存開銷,統(tǒng)一了某些具體操作的邏輯烁登,方便管理怯屉。開發(fā)中常用構(gòu)造單例的方法有兩種@sychronizeddispatch_once

@sychronized

使用@sychronized構(gòu)造單例通常如下:

static SomeClass * instance = nil;
+ (instancetype)shareInstance {
    @synchronized(self) {
        if (instance == nil) {
            instance = [[SomeClass alloc] init];
        }
    }
    return instance;
}

@sychronized是一個編譯器指令饵沧,方便我們對臨界區(qū)提供互斥鎖(mutex locks )锨络。也就是說在多線程并發(fā)訪問臨界區(qū)(@synchronized(self) {//臨界區(qū)})時,它保證同一時刻只有一個線程處于臨界區(qū)中狼牺,其他線程阻塞等待羡儿。那么@sychronized如何標(biāo)識一個互斥鎖?蘋果文檔中說了是钥,@sychronized可以使用任意的Objective-C對象作為互斥鎖的標(biāo)識符(lock token)掠归。那么問題來了,如果互斥鎖的標(biāo)識符不一樣呢(動態(tài)的)悄泥?比如下面這樣:

- (void)instantMethod:(id)lockTocken
{
    @synchronized(lockTocken)
    {
        //臨界區(qū)
    }
}

假設(shè)現(xiàn)在有多個線程并發(fā)調(diào)用上面的實(shí)例方法- (void)instantMethod:(id)lockTocken;虏冻,它們分別為A、B弹囚、C和D線程厨相,其中A、B調(diào)用該方法時候傳入的lockTocken一樣,C蛮穿、D傳入的lockTocken一樣庶骄。那么答案是只有標(biāo)識變量(lockTocken)一樣的線程才會互斥,標(biāo)識變量(lockTocken)不一樣的線程相互之間沒有影響践磅〉サ螅回到最早的例子,其中使用了self(類對象)作為互斥鎖的標(biāo)識符府适,由此可見羔飞,多進(jìn)程并發(fā)訪問,使用的互斥鎖是一樣的细溅,并且在第一個進(jìn)入臨界區(qū)的線程初始化instance后褥傍,其后進(jìn)入的線程就不會再次初始化(instance不再是nil)儡嘶,保證了SomeClass類只有一個實(shí)例喇聊。

好奇的你一定會想,既然@synchronized是編譯器指令蹦狂,那么編譯器對這段代碼做了什么誓篱?

有如下代碼:

id obj = ...
@synchronized(obj) {
    //臨界區(qū)
}

clang -rewrite-objc之后:

id obj = ...
{ id _rethrow = 0; id _sync_obj = (id)obj; objc_sync_enter(_sync_obj);
    try {
        struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
            ~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
            id sync_exit;
        } _sync_exit(_sync_obj);
        
        
    } catch (id e) {_rethrow = e;}
    { struct _FIN { _FIN(id reth) : rethrow(reth) {}
        ~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
        id rethrow;
    } _fin_force_rethow(_rethrow);}
}

即編譯器把@synchronized相關(guān)代碼轉(zhuǎn)成下面這一大坨東西。代碼的關(guān)鍵在兩個函數(shù)的調(diào)用:objc_sync_exitobjc_sync_enter凯楔。代碼的邏輯還是比較好理解窜骄,想深入了解的可以看下這篇博文


dispatch_once

使用dispatch_once構(gòu)造單例通常如下:

static SomeClass * instance = nil;
+ (instancetype)shareInstance {
    static dispatch_once_t onceTocken;
    dispatch_once(&onceTocken, ^{
        instance = [[SomeClass alloc] init];
    });
    return instance;
}

dispatch_once是GCD中提供的函數(shù)摆屯,通常使用它來初始化全局?jǐn)?shù)據(jù)(單例)邻遏,它接受兩個參數(shù),dispatch_once_t *類型的謂語和dispatch_block_t類型的block(block中的代碼就是臨界區(qū))虐骑。文檔中說到准验,dispatch_once_t *類型的謂語用于測試block是否已經(jīng)執(zhí)行結(jié)束或者還沒與執(zhí)行,它配合上dispatch_once函數(shù)保證了在applecation的生命周期中block只會運(yùn)行一次廷没,并且是線程安全的糊饱。可以看到dispatch_once@synchronized一樣颠黎,是線程安全另锋,不同指出在于@synchronized的臨界區(qū)代碼可能在application生命周期中多次調(diào)用,而dispatch_once只會調(diào)用一次(使用dispatch_once_t *類型的謂語做判斷)狭归。因此@synchronized的臨界區(qū)代碼要判斷instance是否是nil夭坪,來判斷是實(shí)例是否已經(jīng)構(gòu)造了。

注意dispatch_once_t *類型的謂語必須是全局變量或者靜態(tài)變量过椎,如果使用自動或者動態(tài)變量(包括Objective-C實(shí)例變量)室梅,dispatch_once的結(jié)果是無法預(yù)知的。


單例思考

單例用著用著就被濫用了。最近正在思考如何對公司APP的分享模塊重構(gòu)竞惋。其中有個類叫SSShareManager柜去,作為的單例,它管理著所有模塊的分享邏輯拆宛。濫用點(diǎn)就在既然是單例嗓奢,卻使用它來保存來自不同模塊的數(shù)據(jù)以及狀態(tài)(比如:分享到微信還是QQ、友盟統(tǒng)計的數(shù)據(jù)等)浑厚,無形中增加了不同模塊之間的耦合股耽。因?yàn)樵谀硞€模塊調(diào)用SSShareManager單例分享之前,還要記得清理其他模塊可能留下的數(shù)據(jù)=钳幅,=物蝙。這篇博文給我很大啟發(fā),其中印象深刻的一段話:

The lesson here is that singletons should be preserved only for state that is global, and not tied to any scope. If state is scoped to any session shorter than “a complete lifecycle of my app,” that state should not be managed by a singleton.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敢艰,一起剝皮案震驚了整個濱河市诬乞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钠导,老刑警劉巖震嫉,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異牡属,居然都是意外死亡票堵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門逮栅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悴势,“玉大人,你說我怎么就攤上這事措伐√叵耍” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵废士,是天一觀的道長叫潦。 經(jīng)常有香客問我,道長官硝,這世上最難降的妖魔是什么矗蕊? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮氢架,結(jié)果婚禮上傻咖,老公的妹妹穿的比我還像新娘。我一直安慰自己岖研,他們只是感情好卿操,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布警检。 她就那樣靜靜地躺著,像睡著了一般害淤。 火紅的嫁衣襯著肌膚如雪扇雕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天窥摄,我揣著相機(jī)與錄音镶奉,去河邊找鬼。 笑死崭放,一個胖子當(dāng)著我的面吹牛哨苛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播币砂,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼建峭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了决摧?” 一聲冷哼從身側(cè)響起亿蒸,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜜徽,沒想到半個月后祝懂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拘鞋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了矢门。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盆色。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祟剔,靈堂內(nèi)的尸體忽然破棺而出隔躲,到底是詐尸還是另有隱情,我是刑警寧澤物延,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布宣旱,位于F島的核電站,受9級特大地震影響叛薯,放射性物質(zhì)發(fā)生泄漏浑吟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一耗溜、第九天 我趴在偏房一處隱蔽的房頂上張望组力。 院中可真熱鬧,春花似錦抖拴、人聲如沸燎字。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽候衍。三九已至笼蛛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蛉鹿,已是汗流浹背伐弹。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榨为,地道東北人惨好。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像随闺,于是被迫代替她去往敵國和親日川。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容