Swift中單例模式詳解

在之前的帖子里聊過狀態(tài)管理有多痛苦芜果,有時(shí)這是不可避免的。一個(gè)狀態(tài)管理的例子大家都很熟悉融师,那就是單例右钾。使用Swift時(shí),有許多方法實(shí)現(xiàn)單例旱爆,這是個(gè)麻煩事舀射,因?yàn)槲覀儾恢滥膫€(gè)最合適。這里我們來(lái)回顧一下單例的歷史怀伦,看一看在Swift中如何正確地實(shí)現(xiàn)單例脆烟。

如果你想直接看看Swift中單例的正確實(shí)現(xiàn)方式,直接跳到帖子最后即可房待。

往事回憶之ObjC單例

Swift是Objective-C的一種自然演變邢羔,它用如下的方式實(shí)現(xiàn)單例:

@interface?Kraken?:?NSObject

@end

@implementation?Kraken

+?(instancetype)sharedInstance?{

static?Kraken?*sharedInstance?=?nil;

static?dispatch_once_t?onceToken;

dispatch_once(&onceToken,?^{

sharedInstance?=?[[Kraken?alloc]?init];

});

returnsharedInstance;

}

@end

在這個(gè)現(xiàn)成方案中,我們可以看到單例的基本結(jié)構(gòu)吴攒。讓我們來(lái)約定一些規(guī)則张抄,這樣便于更好的理解砂蔽。

單例規(guī)則

關(guān)于單例洼怔,有三個(gè)重要的準(zhǔn)則需要牢記:

1. 單例必須是唯一的(要不怎么叫單例?) 在程序生命周期中只能存在一個(gè)這樣的實(shí)例左驾。單例的存在使我們可以全局訪問狀態(tài)镣隶。例如:

NSNotificationCenter, UIApplication和NSUserDefaults极谊。

2. 為保證單例的唯一性,單例類的初始化方法必須是私有的安岂。這樣就可以避免其他對(duì)象通過單例類創(chuàng)建額外的實(shí)例轻猖。

3. 考慮到規(guī)則1,為保證在整個(gè)程序的生命周期中值有一個(gè)實(shí)例被創(chuàng)建域那,單例必須是線程安全的咙边。并發(fā)有時(shí)候確實(shí)挺復(fù)雜,簡(jiǎn)單說(shuō)來(lái)次员,如果單例的代碼不正確败许,如果有兩個(gè)線程同時(shí)實(shí)例化一個(gè)單例對(duì)象,就可能會(huì)創(chuàng)建出兩個(gè)單例對(duì)象淑蔚。也就是說(shuō)市殷,必須保證單例的線程安全性,才可以保證其唯一性刹衫。通過調(diào)用dispatch_once醋寝,即可保證實(shí)例化代碼只運(yùn)行一次。

在程序中保持單例的唯一性带迟,只初始化一次音羞,這樣并不難。帖子的余下部分中邮旷,需要記谆蒲 :?jiǎn)卫龑?shí)現(xiàn)要滿足隱藏的dispatch_once規(guī)則。

Swift單例

自Swift 1.0開始婶肩,創(chuàng)建單例有很多種方法办陷。這些鏈接中已經(jīng)有很詳盡的描述,比如

https://github.com/hpique/SwiftSingleton律歼,http://stackoverflow.com/questions/24024549/dispatch-once-singleton-model-in-swift

https://developer.apple.com/swift/blog/?id=7民镜。但是誰(shuí)喜歡點(diǎn)鏈接呢?先劇透一下吧:總共有4個(gè)版本险毁。我們來(lái)清點(diǎn)一下:

1. 最丑陋方法(Swift皮制圈,Objective-C心)

class TheOneAndOnlyKraken {

classvarsharedInstance:?TheOneAndOnlyKraken?{

struct?Static?{

staticvaronceToken:?dispatch_once_t?=?0

staticvarinstance:?TheOneAndOnlyKraken??=?nil

}

dispatch_once(&Static.onceToken)?{

Static.instance?=?TheOneAndOnlyKraken()

}

returnStatic.instance!

}

}

這個(gè)版本是Objective-C的直接移植版。我認(rèn)為它不好看是因?yàn)镾wift本該更簡(jiǎn)潔畔况、更有描述力鲸鹦。不要做個(gè)搬運(yùn)工,要做就做的更好跷跪。

2. 結(jié)構(gòu)體方法(“新瓶裝老酒)


Swift 1.0時(shí)馋嗜,不支持靜態(tài)類變量,那時(shí)這個(gè)方法是不得已而為之吵瞻。但使用結(jié)構(gòu)體葛菇,就可以支持這個(gè)功能甘磨。因?yàn)殪o態(tài)變量的限制,我們被約束在這樣的一個(gè)模型中眯停。這比Objective-C移植版本好一些济舆,但還不夠好。有趣的是莺债,在Swift 1.2發(fā)布幾個(gè)月后滋觉,我還可以看到這種寫法。在那之后齐邦,反而更多了椎瘟。

3.全局變量方法(“單行單例”方法)

private?let?sharedKraken?=?TheOneAndOnlyKraken()

class?TheOneAndOnlyKraken?{

classvarsharedInstance:?TheOneAndOnlyKraken?{

return ? sharedKraken

? ? ? ? ?}

?}

在Swift 1.2以后,我們有了訪問權(quán)限設(shè)置(access control specifiers) 的功能和靜態(tài)類成員(static class members)侄旬。這意味著我們終于可以擺脫混亂的全局變量肺蔚、全局命名空間,也不會(huì)發(fā)生命名空間沖突了儡羔。這個(gè)版本看起來(lái)更Swiftier一點(diǎn)宣羊。

現(xiàn)在,你可能會(huì)有疑問:為何看不到dispatch_once汰蜘?根據(jù)Apple Swift博客中的說(shuō)法仇冯,以上方法都自動(dòng)滿足dispatch_once規(guī)則。這里有個(gè)帖子可以證明dispatch_once規(guī)則一直在起作用族操。

“全局變量(還有結(jié)構(gòu)體和枚舉體的靜態(tài)成員)的Lazy初始化方法會(huì)在其被訪問的時(shí)候調(diào)用一次苛坚。類似于調(diào)用'dispatch_once'以保證其初始化的原子性。這樣就有了一種很酷的'單次調(diào)用'方式:只聲明一個(gè)全局變量和私有的初始化方法即可色难∑貌眨”--來(lái)自Apple's Swift Blog

(“The lazy initializer for a global variable (also for static members of structs and enums) is run the first time that global is accessed, and is launched as `dispatch_once` to make sure that the initialization is atomic. This enables a cool way to use `dispatch_once` in your code: just declare a global variable with an initializer and mark it private.”)

這就是Apple官方文檔給我們的所有信息,但這些已經(jīng)足夠證明全局變量和結(jié)構(gòu)體/枚舉體的靜態(tài)成員是支持”dispatch_once”特性的〖侠颍現(xiàn)在娇昙,我們相信使用全局變量來(lái)“懶包裝”單例的初始化方法到dispatch_once代碼塊中是100%安全的。但是對(duì)于靜態(tài)類變量來(lái)說(shuō)笤妙,情況又如何冒掌?

這個(gè)問題帶我們到更激動(dòng)人心的思考中去:

正確的方法(也即是“單行單例法”)現(xiàn)在已經(jīng)被證明正確。

class?TheOneAndOnlyKraken?{

static?let?sharedInstance?=?TheOneAndOnlyKraken()

}

到此為止蹲盘,我們已經(jīng)做了許多研究工作股毫。這個(gè)帖子的靈感來(lái)源于我們?cè)贑apital One的一次對(duì)話:結(jié)對(duì)編程review代碼的過程中,我們?cè)噲D找到在App中使用Swift編寫正確召衔、一致的單例方法铃诬。我們知道編寫單例的正確方法,但是無(wú)法用理論來(lái)證明。沒有足夠的文檔支持氧急,想證明方法的正確是徒勞的。在網(wǎng)上或博客圈中沒有足夠多的信息的話毫深,這只能是一家之言吩坝,大家都知道如果網(wǎng)上查不到信息,就不會(huì)相信哑蔫。這點(diǎn)讓我很難過钉寝。

我搜索了許多信息,甚至翻到了google搜索結(jié)果的10多頁(yè)闸迷,還是一無(wú)所獲嵌纲。難道沒有人發(fā)帖證明單行單利方法的正確性?可能有人發(fā)過腥沽,但是太難被發(fā)現(xiàn)了逮走。

因此我決定將各種單例都寫一變,然后在運(yùn)行時(shí)加入斷點(diǎn)來(lái)觀測(cè)今阳。

分析了每個(gè)stack trace的記錄后师溅,我發(fā)現(xiàn)了有趣的東西——證據(jù)!

來(lái)看看截圖:

使用全局單例方法

使用單行單例方法

第一張圖片展示了使用全局實(shí)例時(shí)的stack trace盾舌。標(biāo)紅的地方需要注意墓臭。在調(diào)用Kraken單例之前,先調(diào)用了swift_once妖谴,接下來(lái)是swift_once_block_invoke窿锉。Apple之前在文檔中已經(jīng)說(shuō)過,“懶實(shí)例化”的全局變量會(huì)被自動(dòng)放在dispatch_once塊中膝舅,我們可以假定說(shuō)的就是這個(gè)東西嗡载。

了解了這些知識(shí),我們來(lái)看看漂亮的單行單例方法。如圖所示,調(diào)用完全一樣重窟。這樣充边,我們就有了證據(jù)證明單行單例方法是正確的。

不要忘記設(shè)置初始化方法為私有

@davedelong镀琉,Apple的Framework傳道者,善意地提醒我:必須保證init方法的私有性,只有這樣挪哄,才能保證單例是真正唯一的,避免外部對(duì)象通過訪問init方法創(chuàng)建單例類的其他實(shí)例琉闪。由于Swift中的所有對(duì)象都是由公共的初始化方法創(chuàng)建的迹炼,我們需要重寫自己的init方法,并設(shè)置其為私有的。這很簡(jiǎn)單斯入,而且不會(huì)破壞到我們優(yōu)雅的單行單例方法砂碉。

class?TheOneAndOnlyKraken?{

static?let?sharedInstance?=?TheOneAndOnlyKraken()

private?init()?{}//This?prevents?others?from?using?the?default?'()'?initializer?for?this?class.

}

這樣做就可以保證編譯器在某個(gè)類嘗試使用()來(lái)初始化TheOneAndOnlyKraken時(shí),拋出錯(cuò)誤:

就是這樣刻两,我們的單行單例增蹭,非常完美!

最近又學(xué)到了將單例類置空的方法,與大家分享一下,代碼如下:

class AppManager {

private static var _sharedInstance: AppManager?

class func getSharedInstance() -> AppManager {

guard let instance = _sharedInstance else {

_sharedInstance = AppManager()

return _sharedInstance!

}

return instance

}

private init() {} //私有化init方法

//銷毀單例對(duì)象

class func destroy() {

_sharedInstance = nil

}

}

結(jié)論

這里回復(fù)一下jtbandes在“top rated answer to swift singletons on Stack Overflow”這個(gè)帖子中的問題:我也找不到哪里有文檔證明let語(yǔ)句可以帶來(lái)線程安全性的好處磅摹。我記得在去年參加WWDC的時(shí)候有類似的說(shuō)法滋迈,沒辦法保證讀者或各位Googler也偶遇到這個(gè)說(shuō)法。希望這個(gè)帖子能幫助大家理解為什么單行單例在Swift中是正確的方法户誓。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 原文地址:http://www.cocoachina.com/swift/20151207/14584.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饼灿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子帝美,更是在濱河造成了極大的恐慌碍彭,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悼潭,死亡現(xiàn)場(chǎng)離奇詭異硕旗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)女责,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門漆枚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人抵知,你說(shuō)我怎么就攤上這事墙基。” “怎么了刷喜?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵残制,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我掖疮,道長(zhǎng)初茶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任浊闪,我火速辦了婚禮恼布,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搁宾。我一直安慰自己折汞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布盖腿。 她就那樣靜靜地躺著爽待,像睡著了一般损同。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸟款,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天膏燃,我揣著相機(jī)與錄音,去河邊找鬼何什。 笑死组哩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的富俄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼而咆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼霍比!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起暴备,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤悠瞬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后涯捻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浅妆,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年障癌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凌外。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涛浙,死狀恐怖康辑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情轿亮,我是刑警寧澤疮薇,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站我注,受9級(jí)特大地震影響按咒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜但骨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一励七、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奔缠,春花似錦呀伙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春雨女,著一層夾襖步出監(jiān)牢的瞬間谚攒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工氛堕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馏臭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓讼稚,卻偏偏與公主長(zhǎng)得像括儒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锐想,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 在使用swift編程語(yǔ)言進(jìn)行iOS應(yīng)用開發(fā)的時(shí)候赠摇,我們常常借助單例來(lái)進(jìn)行狀態(tài)管理固逗,但由于實(shí)現(xiàn)單例的方法很多,問題就...
    突然自我閱讀 624評(píng)論 0 0
  • 在之前的帖子里聊過狀態(tài)管理有多痛苦藕帜,有時(shí)這是不可避免的烫罩。一個(gè)狀態(tài)管理的例子大家都很熟悉,那就是單例洽故。使用Swift...
    TomatosX閱讀 1,037評(píng)論 0 1
  • 盡管在我之前的博文里我就寫過關(guān)于管理狀態(tài)的那些坑贝攒,但是有時(shí)候我們就是無(wú)法避免它們。其中一類管理狀態(tài)的方式我們耳熟能...
    一黑閱讀 300評(píng)論 0 1
  • 往事回憶之ObjC單例Swift是Objective-C的一種自然演變时甚,它用如下的方式實(shí)現(xiàn)單例: 在這個(gè)現(xiàn)成方案中...
    王小賓閱讀 3,163評(píng)論 0 5
  • 問題 最近排查一個(gè)crash 問題饿这,讀了一下crash Log以后,發(fā)現(xiàn)堆棧報(bào)的錯(cuò)誤信息非常奇怪撞秋。相似在對(duì)一個(gè)單例...
    還是那個(gè)西海閱讀 10,046評(píng)論 0 9