iOS 單例模式

單例模式作用

  • 可以保證在程序運(yùn)行過程中,一個類只有一個實(shí)例鳖目,而且該實(shí)例易于供外界使用
  • 從而方便地控制了實(shí)例個數(shù)领迈,并節(jié)約系統(tǒng)資源

單例模式使用場合

  • 在整個引用程序中,共享一份資源(這份資源只需要創(chuàng)建初始化1次衷蜓,只分配一次存儲空間)
    • 例如:背景音樂磁浇,音頻調(diào)節(jié)器等

單例的簡單使用

  • 使用單例的目的就是為了要在程序運(yùn)行過程中朽褪,共享一份資源缔赠,且這份資源只會初始化一次嗤堰,只分配一次存儲空間,節(jié)約系統(tǒng)資源告匠;先來看一下平時我們創(chuàng)建對象時,內(nèi)存地址的變化情況:
創(chuàng)建對象內(nèi)存分配地址演示

1.這里用SJTools這個類來演示

//  創(chuàng)建SJTools類
    SJTools *tool1 = [[SJTools alloc] init];
    
    SJTools *tool2 = [SJTools new];
    
    SJTools *tool3 = [[SJTools alloc] init];
    
    NSLog(@"\ntool1%@:\ntool2%@:\ntool3%@:",tool1, tool2, tool3);

執(zhí)行結(jié)果:從打印的內(nèi)存地址可以看出——每次創(chuàng)建同一個類,都會分配新的內(nèi)存地址行贪,這在某些場合是沒有必要的(比如播放音樂建瘫,一般我們不會有在同一個播放器同時播放多首歌的情況出現(xiàn))啰脚,而且系統(tǒng)的資源是有限的,特別是移動設(shè)備粒梦,所以前輩們總結(jié)出新的模式——單例

Snip20160418_1.png
單例創(chuàng)建演示

1.這里依舊使用SJTools這個類演示

  • 現(xiàn)在我們的目的是類無論創(chuàng)建多少次匀们,都只分配一次存儲空間泄朴,而分配存儲空間的操作是在alloc:中執(zhí)行祖灰,所以我們要重寫類中的alloc:類方法
  • 那么我們通過以下幾種思路來實(shí)現(xiàn)單例
+ (instancetype)alloc
+ (instancetype)allocWithZone:(struct _NSZone *)zone

在重寫alloc:過程中畔规,我們發(fā)現(xiàn)有個allocWithZone:方法,這個和alloc:方法有什么區(qū)別呢叁扫?——其實(shí),alloc:方法內(nèi)部最終會去調(diào)用allocWithZone:方法來分配存儲空間沈跨,所以為了能更深層控制,我們放棄重寫alloc:方法软驰,直接重寫allocWithZone:方法。

思路一(ARC模式下單例模式的實(shí)現(xiàn))

1.因?yàn)槭穷惙椒ň牢猓也幌胪饷娅@取到這個變量戴已,所以我們先定義一個靜態(tài)變量

//  聲明一個靜態(tài)變量(不然外界獲取到)
static SJTools *_instance;

1.2 重寫allocWithZone:方法

1.2.1 第一種方式 —— 懶加載

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{    
    //  考慮到使用時可能在多條線程同時執(zhí)行此方法任務(wù)糖儡,那么就可能引發(fā)線程安全問題怔匣,所以我們需要對線程進(jìn)行加鎖操作
    @synchronized(self) {
        if (_instance == nil) { //  如果為nil就執(zhí)行以下操作
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}

1.2.2 第二種方式 —— GCD一次性代碼

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    //  我們也可以使用GCD提供給我們的一次性代碼函數(shù)來實(shí)現(xiàn)每瞒,因?yàn)樗旧砭褪蔷€程安全的
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    
    return _instance;
}

到這里剿骨,簡單的單例就實(shí)現(xiàn)了,但是還有一些問題需要解決挤庇,先不管罚随,先Run來試一下是不是管用

執(zhí)行結(jié)果:從結(jié)果可以看出淘菩,確實(shí)所有創(chuàng)建的操作只分配了一次內(nèi)存

Snip20160418_3.png
  • 為了讓我們的單例嚴(yán)謹(jǐn)一點(diǎn)潮改,我們還要考慮copy這種情況腹暖,所以我們還要重寫copy和mutableCopy

1.1 要重寫copy和mutableCopy方法,必須先遵守協(xié)議

@interface SJTools()<NSCopying, NSMutableCopying>

1.2 重寫copy和mutableCopy方法

- (id)copyWithZone:(NSZone *)zone
{
    //  直接返回變量即可锹杈,因?yàn)橹挥袆?chuàng)建了對象惊暴,才能使用copy方法
    return _instance;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    return _instance;
}
  • 為了供外界方便訪問饮六,我們還需要提供相應(yīng)的調(diào)用方法,一般我們會提供給外界一個類方法供外界使用辛蚊,這也牽扯到命名的問題真仲,怎樣命名才規(guī)范袒餐,這里順便提一下
    • 一般常見的命名形式有這幾種 —— share 灸眼、share + 類名 、default霉囚、default + 類名
      • 優(yōu)點(diǎn):
        • 減少溝通成本
        • 方便外界訪問

1.在.h文件中聲明

/**
 *  獲取單例對象
 */
+ (instancetype)shareSJTools;

2.在.m文件中實(shí)現(xiàn)

+ (instancetype)shareSJTools
{
    //  返回實(shí)例對象
    return [[self alloc] init];
}
  • 到這來我們的單例模式就實(shí)現(xiàn)了盈罐,但是上面的方式在MRC環(huán)境下就不好用了盅粪,那有沒有同時在ARC和MRC中都可使用的方法呢票顾?下面我們就來解決這樣的問題帆调。

思路二(MRC下單例模式的實(shí)現(xiàn))

首先番刊,需要MRC的環(huán)境,需要先修改一下XCode配置

Snip20160418_4.png

接下來需要修改下前面創(chuàng)建對象的代碼鸭廷,讓其符合MRC規(guī)則,并運(yùn)行

    //  創(chuàng)建SJTools類
    SJTools *tool1 = [[SJTools alloc] init];
    
    [tool1 release];
    
    SJTools *tool2 = [SJTools new];
    [tool2 release];
    
    SJTools *tool3 = [[SJTools alloc] init];
    [tool3 release];
    
    NSLog(@"\ntool1%@:\ntool2%@:\ntool3%@:",tool1, tool2, tool3);

執(zhí)行結(jié)果:編譯器提示我們消息發(fā)送給已經(jīng)釋放的對象靴姿,這是因?yàn)槲覀冊趧?chuàng)建對象后磁滚,對其進(jìn)行了release操作垂攘,這樣對象的計數(shù)器為0晒他,系統(tǒng)就將會其釋放逸贾,所以在設(shè)計單例時我們還需要考慮MRC的環(huán)境。

Snip20160418_5.png
  • 因?yàn)閱卫纳芷谑菑谋粍?chuàng)建的那一刻起铝侵,到程序運(yùn)行結(jié)束灼伤,也就是說,我們可以不用理會MRC中的規(guī)則咪鲜,但是又不想破壞使用者的使用習(xí)慣狐赡,那么我們可以在類中重寫retainrelease疟丙, retainCount方法颖侄,使其不受外界影響。
- (oneway void)release
{
    //  因?yàn)槲覀儾焕頃﨧RC規(guī)則享郊,所以在release方法中可以什么都不做
}

- (instancetype)retain
{
    //  因?yàn)閞etain方法要求返回一個instancetype返回值,我們直接返回變量
    return _instance;
}

- (NSUInteger)retainCount
{
    //  在之前的一些早期的MRC項(xiàng)目中發(fā)現(xiàn)炊琉,單例中普遍會直接給retaiCount一個MAXFLOAT作為返回值展蒂,這樣做應(yīng)該是為了讓外界認(rèn)為這個類是單例,進(jìn)一步減少溝通成本
    return MAXFLOAT;
}

執(zhí)行結(jié)果:這樣程序就能正常運(yùn)行了

Snip20160418_6.png

為了讓單例在ARCMRC模式下都能愉快使用温自,我們可以使用宏來判斷當(dāng)前系統(tǒng)環(huán)境玄货,并做出對應(yīng)處理

#ifdef __has_feature(objc_arc)

//  ARC環(huán)境
#else

//  MRC環(huán)境
- (oneway void)release
{
    //  因?yàn)槲覀儾焕頃﨧RC規(guī)則,所以在release方法中可以什么都不做
}

- (instancetype)retain
{
    //  因?yàn)閞etain方法要求返回一個instancetype返回值悼泌,我們直接返回變量
    return _instance;
}

- (NSUInteger)retainCount
{
    //  在之前的一些早期的MRC項(xiàng)目中發(fā)現(xiàn)松捉,單例中普遍會直接給retaiCount一個MAXFLOAT作為返回值,這樣做應(yīng)該是為了讓外界認(rèn)為這個類是單例馆里,進(jìn)一步減少溝通成本
    return MAXFLOAT;
}

#endif

到此隘世,我們的單例就可以歡快地使用了可柿。


單例模式使用注意

  • 單例是不可繼承的,我們可以來演示一下

首先丙者,新建一個SJNewTools類复斥,并讓他繼承SJTools,然后打印一下創(chuàng)建SJTools對象和SJNewTools對象后的內(nèi)存地址

    //  創(chuàng)建SJTools類
    SJTools *tool1 = [SJTools shareSJTools];
    
    //  創(chuàng)建SJNewTools類
    SJNewTools *newTool1 = [SJNewTools shareSJTools];
    
    NSLog(@"\ntool1%@:\nnewTool1%@:\n",tool1, newTool1);

執(zhí)行結(jié)果:打印出來的內(nèi)存地址是相同的械媒,而且他們的真實(shí)類型都是SJTools

Snip20160418_7.png

為什么會這樣目锭?

其實(shí)是因?yàn)?code>alloc:方法內(nèi)部會調(diào)用allocWithZone:方法,在allocWithZone:內(nèi)會對_instance這個變量進(jìn)行初始化纷捞,給它分配存儲空間痢虹;在這個階段的時候,系統(tǒng)會去判斷這個變量的真實(shí)類型主儡,因?yàn)楫?dāng)前變量的類型是SJTools類型奖唯,所以_instance變量就是SJTools類型,后面SJNewTools繼承了SJTools糜值,但是內(nèi)部使用的是同一個變量丰捷,就造成了上面的情況,所以寂汇,單例是不能繼承的病往。


懶人福音 —— 單例的復(fù)用

  • 在開發(fā)中,一般出現(xiàn)像這樣可能復(fù)用的代碼健无,而又不能繼承的方式荣恐,那簡直對我們這種懶人的晴天霹靂!難道每次要使用單例都要敲著這樣一大串惡心的代碼么累贤?其實(shí)叠穆,我們還可以用宏來解決這樣的事情嘛O(∩_∩)O

首先,因?yàn)閱卫梢苑譃?部分臼膏,一部分是.h文件硼被,一部分是.m文件,所以渗磅,宏要分開來寫 —— 先創(chuàng)建一個.h文件

Singleton.h文件
1.因?yàn)槲覀冊谥恍枰峁┮粋€方法給外界使用嚷硫,而且為了讓其用起來更加清晰,我們給其加入?yún)?shù)
宏內(nèi)怎么接收和使用參數(shù)呢始鱼?

  • 格式:
    • #define 宏名(參數(shù)名) ##參數(shù)名
#define SingletonH(name) +(instancetype)share##name;

2.將之前我們在SJTools類的.m文件所做的操作拷貝過來仔掸,但會發(fā)現(xiàn)除了第一行以外的所有代碼段都是沒有列入宏的范圍內(nèi)(顏色不同);這時候需要使用""符來進(jìn)行連接医清,同樣起暮,我們要讓外界傳入?yún)?shù)來改變方法名,讓其與.h文件相對應(yīng)会烙;同時變量的類型直接修改為id類型即可负懦。

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

這樣宏就已經(jīng)做好了筒捺,我們只需要在要使用單例的類中先引入頭文件,然后在.h文件中這樣使用

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

@interface SJTools : NSObject

SingletonH(SJTools)

@end

.m文件中這樣使用就可以了

@implementation SJTools

SingletonW(SJTools)

@end

為了使我們的宏能同時ARC和MRC纸厉,我們需要將宏判斷部分也加入到單例宏中系吭,但是宏判斷是不能內(nèi)嵌的,所以只能先判斷環(huán)境颗品,再分別進(jìn)行相應(yīng)操作

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

#if __has_feature(objc_arc)

//  ARC環(huán)境
#define SingletonW(name) static id _instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        _instance = [super allocWithZone:zone];\
    });\
    \
    return _instance;\
}\
+ (instancetype)share##name\
{\
    return [[self alloc] init];\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
    return _instance;\
}\
- (id)mutableCopyWithZone:(NSZone *)zone\
{\
    return _instance;\
}

#else

//  MRC環(huán)境
#define SingletonW(name) static id _instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
\
return _instance;\
}\
+ (instancetype)share##name\
{\
return [[self alloc] init];\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
- (id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
- (oneway void)release\
{\
}\
- (instancetype)retain\
{\
    return _instance;\
}\
- (NSUInteger)retainCount\
{\
    return MAXFLOAT;\
}

#endif

好了肯尺,這樣我們的單例宏就完成了,只要在需要的項(xiàng)目中導(dǎo)入這個宏抛猫,然后在需要的地方使用宏就可以了蟆盹!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市闺金,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌峰档,老刑警劉巖败匹,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異讥巡,居然都是意外死亡掀亩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門欢顷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來槽棍,“玉大人,你說我怎么就攤上這事抬驴×镀撸” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵布持,是天一觀的道長豌拙。 經(jīng)常有香客問我,道長题暖,這世上最難降的妖魔是什么按傅? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮胧卤,結(jié)果婚禮上唯绍,老公的妹妹穿的比我還像新娘。我一直安慰自己枝誊,他們只是感情好况芒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著侧啼,像睡著了一般牛柒。 火紅的嫁衣襯著肌膚如雪堪簿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天皮壁,我揣著相機(jī)與錄音椭更,去河邊找鬼。 笑死蛾魄,一個胖子當(dāng)著我的面吹牛虑瀑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滴须,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼舌狗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扔水?” 一聲冷哼從身側(cè)響起痛侍,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎魔市,沒想到半個月后主届,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡待德,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年君丁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片将宪。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡绘闷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出较坛,到底是詐尸還是另有隱情印蔗,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布燎潮,位于F島的核電站喻鳄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏确封。R本人自食惡果不足惜除呵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爪喘。 院中可真熱鬧颜曾,春花似錦、人聲如沸秉剑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诡曙,卻和暖如春臀叙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背价卤。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工劝萤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慎璧。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓床嫌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親胸私。 傳聞我的和親對象是個殘疾皇子厌处,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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

  • 1 單例模式 它是一種設(shè)計模式(常見的設(shè)計模式有:觀察者模式、工廠模式岁疼、門面模式等)阔涉。單例設(shè)計模式中,一個類只有一...
    歲與禾閱讀 914評論 5 9
  • 一. 單例模式簡介 單例模式的作用可以保證在程序運(yùn)行過程五续,一個類只有一個實(shí)例洒敏,而且該實(shí)例易于供外界訪問從而方便地控...
    xx_cc閱讀 50,227評論 15 146
  • 原鏈接:http://www.reibang.com/p/4867dc92337e原作者:僅供我個人收藏學(xué)習(xí),原博...
    油菜花花花花閱讀 333評論 0 0
  • 簡介: 單例模式是一種常用的軟件設(shè)計模式疙驾。在它的核心結(jié)構(gòu)中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統(tǒng)...
    RunnerFL閱讀 632評論 0 0
  • 1 2 3 4 5 6 aaa ssss dd1.adda2.adf3.ada adfafdafaaaaa
    戴弢001閱讀 136評論 0 0