設(shè)計模式系列14--單例模式

在開發(fā)中經(jīng)常會用到單例設(shè)計模式熊响,目的就是為了在程序的整個生命周期內(nèi)昧碉,只會創(chuàng)建一個類的實例對象英染,而且只要程序不被殺死,該實例對象就不會被釋放被饿。下面我們來看看單例的概念四康、用途、如何創(chuàng)建狭握,以便加深理解闪金。

定義

保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

作用

  • 在應(yīng)用這個模式時论颅,單例對象的類必須保證只有一個實例存在哎垦。許多時候整個系統(tǒng)只需要擁有一個的全局對象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為恃疯。比如在APP開發(fā)中我們可能在任何地方都要使用用戶的信息漏设,那么可以在登錄的時候就把用戶信息存放在一個文件里面,這些配置數(shù)據(jù)由一個單例對象統(tǒng)一讀取今妄,然后服務(wù)進(jìn)程中的其他對象再通過這個單例對象獲取這些配置信息郑口。這種方式簡化了在復(fù)雜環(huán)境下的配置管理。
  • 有的情況下盾鳞,某個類可能只能有一個實例犬性。比如說你寫了一個類用來播放音樂,那么不管任何時候只能有一個該類的實例來播放聲音腾仅。再比如乒裆,一臺計算機(jī)上可以連好幾個打印機(jī),但是這個計算機(jī)上的打印程序只能有一個推励,這里就可以通過單例模式來避免兩個打印任務(wù)同時輸出到打印機(jī)中鹤耍,即在整個的打印過程中我只有一個打印程序的實例。

創(chuàng)建單例

有兩種方法來創(chuàng)建單例吹艇,下面分別介紹

1惰蜜、GCD方式創(chuàng)建單例

static id _instance;   
   
+ (instancetype)allocWithZone:(struct _NSZone *)zone  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [super allocWithZone:zone];   
    });   
    return _instance;   
}   
   
+ (instancetype)sharedInstance  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [[self alloc] init];   
    });   
    return _instance;   
}   
   
- (id)copyWithZone:(NSZone *)zone   
{   
    return _instance;   
}  
  
- (id)mutableCopyWithZone:(NSZone *)zone {   
    return _instance;   
}

2、互斥鎖方式

static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}
+ (instancetype)sharedInstance
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}

上面兩種方式都可以創(chuàng)建單例受神,而且保證了用戶不管是通過shareInstance方法抛猖,還是alloc、copy方法得到的實例都是一樣的。

上面代碼的關(guān)鍵之處就在于如何在多線程情況下保證創(chuàng)建的單例還是同一個财著。

我們先看看在GCD情況下联四,如果不使用dispatch_once和同步鎖創(chuàng)建單例會出現(xiàn)什么問題,去掉兩者后創(chuàng)建單例的代碼如下

+ (instancetype)sharedInstance  
{   
 if (_instance == nil) {
     _instance = [[self alloc] init];
  }
} 

假設(shè)此時有兩條線程:線程1和線程2撑教,都在調(diào)用shareInstance方法來創(chuàng)建單例朝墩,那么線程1運(yùn)行到if (_instance == nil)出發(fā)現(xiàn)_instance = nil,那么就會初始化一個_instance,假設(shè)此時線程2也運(yùn)行到if的判斷處了伟姐,此時線程1還沒有創(chuàng)建完成實例_instance收苏,所以此時_instance = nil還是成立的,那么線程2又會創(chuàng)建一個_instace愤兵。

此時就創(chuàng)建了兩個實例對象鹿霸,導(dǎo)致問題。

解決辦法1秆乳、使用dispatch_once

dispatch_once保證程序在運(yùn)行過程中只會被運(yùn)行一次懦鼠,那么假設(shè)此時線程1先執(zhí)行shareInstance方法,創(chuàng)建了一個實例對象屹堰,線程2就不會再去執(zhí)行dispatch_once的代碼了肛冶。從而保證了只會創(chuàng)建一個實例對象。

解決辦法2扯键、使用互斥鎖

假設(shè)此時線程1在執(zhí)行shareInstance方法睦袖,那么synchronize大括號內(nèi)創(chuàng)建單例的代碼,如下所示:

if (_instance == nil) {
            _instance = [[self alloc] init];
        }

就會被當(dāng)做一個任務(wù)被加上了一把鎖忧陪。此時假設(shè)線程2也想執(zhí)行shareInstance方法創(chuàng)建單例扣泊,但是看到了線程1加的互斥鎖,就會進(jìn)入睡眠模式嘶摊。等到線程1執(zhí)行完畢,才會被喚醒评矩,然后去執(zhí)行上面所示的創(chuàng)建單例的代碼叶堆,但是此時_instance !=nil,所以不會再創(chuàng)建新的實例對象了。從而保證只會創(chuàng)建一個實例對象斥杜。

但是互斥鎖會影響性能虱颗,所以最好還是使用GCD方式創(chuàng)建單例。


宏創(chuàng)建單例

如果我們需要在程序中創(chuàng)建多個單例蔗喂,那么需要在每個類中都寫上一次上述代碼忘渔,非常繁瑣。

我們可以使用宏來封裝單例的創(chuàng)建缰儿,這樣任何類需要創(chuàng)建單例畦粮,只需要一行代碼就搞定了。

實現(xiàn)代碼

Singleton.h文件
==================================

#define SingletonH(name) + (instancetype)shared##name;

#define SingletonM(name) \
static id _instance; \
 \
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        _instance = [super allocWithZone:zone]; \
    }); \
    return _instance; \
} \
 \
+ (instancetype)shared##name \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        _instance = [[self alloc] init]; \
    }); \
    return _instance; \
} \
 \
- (id)copyWithZone:(NSZone *)zone \
{ \
    return _instance; \
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone { \
    return _instance; \
}

如何調(diào)用

假設(shè)我們要在類viewcontroller中使用,調(diào)用方法如下:

viewcontroller.h文件
===========================

#import <UIKit/UIKit.h>
#import "Singleton.h"

@interface ViewController : UIViewController
SingletonH(viewController)
@end



viewcontroller.m文件
===========================

@interface ViewController ()

@end

@implementation ViewController

SingletonM(ViewController)

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"%@ %@ %@ %@", [ViewController sharedViewController],[ViewController sharedViewController], [[ViewController alloc] init],[[ViewController alloc] init]);
    
}

@end


輸出結(jié)果

 <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00>

可以看到四個對象的內(nèi)存地址完全一樣宣赔,說明是同一個對象

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末预麸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子儒将,更是在濱河造成了極大的恐慌吏祸,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钩蚊,死亡現(xiàn)場離奇詭異贡翘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)砰逻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進(jìn)店門床估,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诱渤,你說我怎么就攤上這事丐巫。” “怎么了勺美?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵递胧,是天一觀的道長。 經(jīng)常有香客問我赡茸,道長缎脾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任占卧,我火速辦了婚禮遗菠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘华蜒。我一直安慰自己辙纬,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布叭喜。 她就那樣靜靜地躺著贺拣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捂蕴。 梳的紋絲不亂的頭發(fā)上譬涡,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機(jī)與錄音啥辨,去河邊找鬼涡匀。 笑死,一個胖子當(dāng)著我的面吹牛溉知,可吹牛的內(nèi)容都是我干的陨瘩。 我是一名探鬼主播腕够,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拾酝!你這毒婦竟也來了燕少?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蒿囤,失蹤者是張志新(化名)和其女友劉穎客们,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體材诽,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡底挫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了脸侥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片建邓。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖睁枕,靈堂內(nèi)的尸體忽然破棺而出官边,到底是詐尸還是另有隱情,我是刑警寧澤外遇,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布注簿,位于F島的核電站,受9級特大地震影響跳仿,放射性物質(zhì)發(fā)生泄漏诡渴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一菲语、第九天 我趴在偏房一處隱蔽的房頂上張望妄辩。 院中可真熱鬧,春花似錦山上、人聲如沸眼耀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畔塔。三九已至,卻和暖如春鸯屿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背把敢。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工寄摆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人修赞。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓婶恼,卻偏偏與公主長得像桑阶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子勾邦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350

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