iOS 單例模式

單例模式,由于其簡(jiǎn)單好用容易理解戳寸、同時(shí)在出問(wèn)題時(shí)也容易定位的特點(diǎn),在開(kāi)發(fā)中經(jīng)常用到的一個(gè)設(shè)計(jì)模式努释,本文主要分享我在自己的代碼中是如何使用單例模式的碘梢。

1、什么是單例模式

單例模式的定義

簡(jiǎn)單的來(lái)說(shuō)伐蒂,一個(gè)單例類(lèi)煞躬,在整個(gè)程序中只有一個(gè)實(shí)例,并且提供一個(gè)類(lèi)方法供全局調(diào)用逸邦,在編譯時(shí)初始化這個(gè)類(lèi)恩沛,然后一直保存在內(nèi)存中,到程序(APP)退出時(shí)由系統(tǒng)自動(dòng)釋放這部分內(nèi)存缕减。

系統(tǒng)為我們提供的單例類(lèi)有哪些雷客?

UIApplication(應(yīng)用程序?qū)嵗?lèi))
NSNotificationCenter(消息中心類(lèi))
NSFileManager(文件管理類(lèi))
NSUserDefaults(應(yīng)用程序設(shè)置)
NSURLCache(請(qǐng)求緩存類(lèi))
NSHTTPCookieStorage(應(yīng)用程序cookies池)

在哪些地方會(huì)用到單例模式

一般在我的程序中,經(jīng)常調(diào)用的類(lèi)桥狡,如工具類(lèi)搅裙、公共跳轉(zhuǎn)類(lèi)等妓局,我都會(huì)采用單例模式;

重復(fù)初始化單例類(lèi)會(huì)怎樣呈宇?

請(qǐng)看下面的例子好爬,我在我的工程中,初始化一次UIApplication,

  [[UIApplication alloc]init];

最后運(yùn)行的結(jié)果是甥啄,程序直接崩潰存炮,并報(bào)了下面的錯(cuò),

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'There can only be one UIApplication instance.'
初始化

所以蜈漓,由此可以確定穆桂,一個(gè)單例類(lèi)只能初始化一次。

2融虽、單例類(lèi)的生命周期

單例實(shí)例在存儲(chǔ)器的中位置

請(qǐng)看下面的表格展示了程序中中不同的變量在手機(jī)存儲(chǔ)器中的存儲(chǔ)位置享完;

位置 存放的變量
臨時(shí)變量(由編譯器管理自動(dòng)創(chuàng)建/分配/釋放的,棧中的內(nèi)存被調(diào)用時(shí)處于存儲(chǔ)空間中有额,調(diào)用完畢后由系統(tǒng)系統(tǒng)自動(dòng)釋放內(nèi)存)
通過(guò)alloc般又、calloc、malloc或new申請(qǐng)內(nèi)存巍佑,由開(kāi)發(fā)者手動(dòng)在調(diào)用之后通過(guò)free或delete釋放內(nèi)存茴迁。動(dòng)態(tài)內(nèi)存的生存期可以由我們決定,如果我們不釋放內(nèi)存萤衰,程序?qū)⒃谧詈蟛裴尫诺魟?dòng)態(tài)內(nèi)存堕义,在ARC模式下,由系統(tǒng)自動(dòng)管理脆栋。
全局區(qū)域 靜態(tài)變量(編譯時(shí)分配倦卖,APP結(jié)束時(shí)由系統(tǒng)釋放)
常量 常量(編譯時(shí)分配,APP結(jié)束時(shí)由系統(tǒng)釋放)
代碼區(qū) 存放代碼

在程序中椿争,一個(gè)單例類(lèi)在程序中只能初始化一次怕膛,為了保證在使用中始終都是存在的,所以單例是在存儲(chǔ)器的全局區(qū)域丘薛,在編譯時(shí)分配內(nèi)存嘉竟,只要程序還在運(yùn)行就會(huì)一直占用內(nèi)存,在APP結(jié)束后由系統(tǒng)釋放這部分內(nèi)存內(nèi)存洋侨。

3舍扰、新建一個(gè)單例類(lèi)

(1)、單例模式的創(chuàng)建方式希坚;
同步鎖 :NSLock
@synchronized(self) {}
信號(hào)量控制并發(fā):dispatch_semaphore_t 
條件鎖:NSConditionLock
dispatch_once_t

考慮數(shù)據(jù)和線程問(wèn)題边苹,蘋(píng)果官方推薦開(kāi)發(fā)者使用dispatch_once_t來(lái)創(chuàng)建單例,那么我就采用dispatch_once_t方法來(lái)創(chuàng)建一個(gè)單例裁僧,類(lèi)名為OneTimeClass个束。

static OneTimeClass *__onetimeClass;
+ (OneTimeClass *)sharedOneTimeClass {
static dispatch_once_t oneToken;

    dispatch_once(&oneToken, ^{

        __onetimeClass = [[OneTimeClass alloc]init];

    });
    return __onetimeClass;
}

4慕购、單例模式的優(yōu)缺點(diǎn)

先說(shuō)優(yōu)點(diǎn):

(1)、在整個(gè)程序中只會(huì)實(shí)例化一次茬底,所以在程序如果出了問(wèn)題沪悲,可以快速的定位問(wèn)題所在;
(2)阱表、由于在整個(gè)程序中只存在一個(gè)對(duì)象殿如,節(jié)省了系統(tǒng)內(nèi)存資源,提高了程序的運(yùn)行效率最爬;

再說(shuō)缺點(diǎn)

(1)涉馁、不能被繼承,不能有子類(lèi)爱致;

(2)烤送、不易被重寫(xiě)或擴(kuò)展(可以使用分類(lèi));

(3)糠悯、同時(shí)帮坚,由于單例對(duì)象只要程序在運(yùn)行中就會(huì)一直占用系統(tǒng)內(nèi)存,該對(duì)象在閑置時(shí)并不能銷(xiāo)毀逢防,在閑置時(shí)也消耗了系統(tǒng)內(nèi)存資源叶沛;

5蒲讯、單例模式詳解

(1)忘朝、重寫(xiě)單例類(lèi)的alloc方法保證這個(gè)類(lèi)只會(huì)被初始化一次

我在viewDidLoad方法中調(diào)用單例類(lèi)的allocinit方法:

[[OneTimeClass alloc]init];

此時(shí)只是報(bào)黃點(diǎn),但是并沒(méi)有報(bào)錯(cuò)判帮,Run程序也可以成功局嘁,這樣的話,就不符合我們最開(kāi)始使用單例模式的初衷來(lái)晦墙,這個(gè)類(lèi)也可以隨便初始化類(lèi)悦昵,為什么呢?因?yàn)槲覀儾](méi)有獲取OneTimeClass類(lèi)的使用實(shí)例晌畅,改進(jìn)代碼:

[OneTimeClass sharedOneTimeClass];
 [[OneTimeClass alloc]init];

這是改進(jìn)后的但指,但是在多人開(kāi)發(fā)時(shí),還是沒(méi)辦法保證抗楔,我們會(huì)先調(diào)用alloc方法棋凳,這樣我們就沒(méi)辦法控制了,但是我們控制OneTimeClass類(lèi)连躏,此時(shí)我們可以重寫(xiě)OneTimeClass類(lèi)的alloc方法,此處在重寫(xiě)alloc方法的處理可以采用斷言或者系統(tǒng)為開(kāi)發(fā)者提供的NSException類(lèi)來(lái)告訴其他的同事這個(gè)類(lèi)是單例類(lèi)剩岳,不能多次初始化。

//斷言
+ (instancetype)alloc {
  NSCAssert(!__onetimeClass, @"OneTimeClass類(lèi)只能初始化一次");
  return [super alloc];
}
//NSException
+ (instancetype)alloc {
   //如果已經(jīng)初始化了
    if (__onetimeClass) {
      NSException *exception = [NSException exceptionWithName:@"提示" reason:@"OneTimeClass類(lèi)只能初始化一次" userInfo:nil];
      [exception raise];
   }
 return [super alloc];
}

此時(shí)在run一次入热,可以看到程序直接崩到main函數(shù)上了拍棕,并按照我之前給的提示報(bào)錯(cuò)晓铆。


初始化

但是,如果我們的程序直接就崩潰了绰播,這樣的做法與開(kāi)發(fā)者開(kāi)發(fā)APP的初衷是不是又相悖了骄噪,作為一個(gè)程序員的目的要給用戶一個(gè)交互友好的APP,而不是一點(diǎn)小問(wèn)題就崩潰蠢箩,當(dāng)然咯腰池,如果想和測(cè)試的妹紙多交流交流,那就忙芒。示弓。。呵萨。奏属。
對(duì)于這種情況,可以用到NSObect類(lèi)提供的load方法和initialize方法來(lái)控制潮峦,
這兩個(gè)方法的調(diào)用時(shí)機(jī):

load方法是在整個(gè)文件被加載到運(yùn)行時(shí)囱皿,在main函數(shù)調(diào)用之前調(diào)用;

initialize方法是在該類(lèi)第一次調(diào)用該類(lèi)時(shí)調(diào)用忱嘹;

為了驗(yàn)證load方法和initialize方法的調(diào)用時(shí)機(jī)嘱腥,我在 Main函數(shù)中打印:

printf("\n\n\n\nmain()");

OneTimeClass類(lèi)的load方法中打泳性谩:

+ (void)load {
    printf("\n\nOneTimeClass load()");
}

OneTimeClass類(lèi)的initialize方法中打映萃谩:

+ (void)initialize {
    printf("\n\nOneTimeClass initialize()");
}

運(yùn)行程序,最后的結(jié)果是础米,load方法先打印出來(lái)分苇,所以可以確定的是load的確是在在main函數(shù)調(diào)用之前調(diào)用的。

load和initialize

這樣的話屁桑,如果我在單例類(lèi)的load方法或者initialize方法中初始化這個(gè)類(lèi)医寿,是不是就保證了這個(gè)類(lèi)在整個(gè)程序中調(diào)用一次呢?

+ (void)load {
  printf("\n\nOneTimeClass load()");
}
+ (void)initialize {
  printf("\nOneTimeClass initialize()\n\n\n");
  [OneTimeClass sharedOneTimeClass];
}

這樣就可以保證sharedOneTimeClass方法是最早調(diào)用的蘑斧。同時(shí)靖秩,再次對(duì)alloc方法修改,無(wú)論在何時(shí)調(diào)用OneTimeClass已經(jīng)初始化了竖瘾,如果再次調(diào)用alloc可直接返回__onetimeClass實(shí)例沟突。

+ (instancetype)alloc {
 if (__onetimeClass) {
        return  __onetimeClass;
    }
 return [super alloc];
}

最后在ViewController中打印調(diào)用OneTimeClasssharedOneTimeClassalloc方法,可以看到Log出來(lái)的內(nèi)存地址是相同的准浴,這就說(shuō)明此時(shí)我的OneTimeClass類(lèi)就只初始化了一次事扭。

   OneTimeClass *onetime1 = [OneTimeClass sharedOneTimeClass];
    NSLog(@"shared:============%@",onetime1);
    OneTimeClass *onetime2 = [[OneTimeClass alloc] init];
    NSLog(@"new:============%@",onetime2);
內(nèi)存地址
(2)、對(duì)new乐横、copy求橄、mutableCopy的處理

方案一:重寫(xiě)這幾個(gè)方法今野,當(dāng)調(diào)用時(shí)提示或者返回OneTimeClass類(lèi)實(shí)例,請(qǐng)參考alloc方法的處理罐农;

方案二:直接禁用這個(gè)方法条霜,禁止調(diào)用這幾個(gè)方法,否則就報(bào)錯(cuò)涵亏,編譯不過(guò)宰睡;

+(instancetype) new __attribute__((unavailable("OneTimeClass類(lèi)只能初始化一次")));
-(instancetype) copy __attribute__((unavailable("OneTimeClass類(lèi)只能初始化一次")));
-(instancetype) mutableCopy  __attribute__((unavailable("OneTimeClass類(lèi)只能初始化一次")));

此時(shí)我在viewDidLoad中調(diào)用new,然后Build,編譯器會(huì)直接給出錯(cuò)誤警告气筋,如下圖:

OneTimeClass類(lèi)只能初始化一次

這樣就解決了單例類(lèi)被多次初始化的問(wèn)題拆内;

(3)、分類(lèi)Category的使用

如果在程序中某個(gè)模塊的業(yè)務(wù)邏輯比較多宠默,此時(shí)可以選擇分類(lèi)Category的方式麸恍,這樣做的好處是:
(1)、減少Controller代碼行數(shù)搀矫,使代碼邏輯更清晰抹沪;
(2)、把同一個(gè)功能業(yè)務(wù)區(qū)分開(kāi)瓤球,利于后期的維護(hù)融欧;
(3)、遇到BUG能快速定位到相關(guān)代碼卦羡;
原則上分類(lèi)Category只能增加和實(shí)現(xiàn)方法噪馏,而不能增加屬性,此處請(qǐng)參考美團(tuán)技術(shù)團(tuán)隊(duì)的博客:深入理解Objective-C:Category

例如虹茶,在我們的APP中逝薪,用到了Socket技術(shù),我在客戶端Socket部分的代碼使用了單例模式蝴罪。由于和服務(wù)器的交互比較多,此時(shí)采用分類(lèi)Category的方式步清,把Socket異常處理要门,給服務(wù)器發(fā)送的協(xié)議,和接受到服務(wù)器的協(xié)議 用三個(gè)分類(lèi)Category來(lái)實(shí)現(xiàn)廓啊。在以后的維護(hù)中如果業(yè)務(wù)復(fù)雜度增加欢搜,或者加了新的業(yè)務(wù)或功能,可繼續(xù)新建一個(gè)分類(lèi)谴轮。這樣既不影響之前的代碼炒瘟,同時(shí)又可以保證新的代碼邏輯清晰。

以上是我在單例模式使用上的一些總結(jié)第步,如果有錯(cuò)誤的地方疮装,請(qǐng)指出缘琅。

本文demo:戳這里
本文參考:細(xì)說(shuō)@synchronized和dispatch_once

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市廓推,隨后出現(xiàn)的幾起案子刷袍,更是在濱河造成了極大的恐慌,老刑警劉巖樊展,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呻纹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡专缠,警方通過(guò)查閱死者的電腦和手機(jī)雷酪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)涝婉,“玉大人太闺,你說(shuō)我怎么就攤上這事∴胰Γ” “怎么了省骂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)最住。 經(jīng)常有香客問(wèn)我钞澳,道長(zhǎng),這世上最難降的妖魔是什么涨缚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任轧粟,我火速辦了婚禮,結(jié)果婚禮上脓魏,老公的妹妹穿的比我還像新娘兰吟。我一直安慰自己,他們只是感情好茂翔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布混蔼。 她就那樣靜靜地躺著,像睡著了一般珊燎。 火紅的嫁衣襯著肌膚如雪惭嚣。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天悔政,我揣著相機(jī)與錄音晚吞,去河邊找鬼。 笑死谋国,一個(gè)胖子當(dāng)著我的面吹牛槽地,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捌蚊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼集畅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起逢勾,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤牡整,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后溺拱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體逃贝,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年迫摔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沐扳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡句占,死狀恐怖沪摄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纱烘,我是刑警寧澤杨拐,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站擂啥,受9級(jí)特大地震影響哄陶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哺壶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一屋吨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧山宾,春花似錦至扰、人聲如沸夜郁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)朴则。三九已至,卻和暖如春绽榛,著一層夾襖步出監(jiān)牢的瞬間嗓节,已是汗流浹背臼节。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工接剩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人萨咳。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓懊缺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹃两,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

  • 單例模式作用 可以保證在程序運(yùn)行過(guò)程中遗座,一個(gè)類(lèi)只有一個(gè)實(shí)例,而且該實(shí)例易于供外界使用 從而方便地控制了實(shí)例個(gè)數(shù)俊扳,并...
    珍此良辰閱讀 1,330評(píng)論 3 8
  • 1 單例模式 它是一種設(shè)計(jì)模式(常見(jiàn)的設(shè)計(jì)模式有:觀察者模式途蒋、工廠模式、門(mén)面模式等)馋记。單例設(shè)計(jì)模式中号坡,一個(gè)類(lèi)只有一...
    歲與禾閱讀 917評(píng)論 5 9
  • 簡(jiǎn)介: 單例模式是一種常用的軟件設(shè)計(jì)模式。在它的核心結(jié)構(gòu)中只包含一個(gè)被稱(chēng)為單例類(lèi)的特殊類(lèi)梯醒。通過(guò)單例模式可以保證系統(tǒng)...
    RunnerFL閱讀 636評(píng)論 0 0
  • 感覺(jué)渾身發(fā)冷宽堆, 是不是要倒下了?
    十八呆閱讀 265評(píng)論 0 0
  • 不論你是直接npm koa還是npm koa-generator這樣的第三方腳手架茸习,如果發(fā)現(xiàn)運(yùn)行最簡(jiǎn)單的demo都...
    哪吒鬧海全靠浪閱讀 264評(píng)論 0 0