iOS開發(fā)-單例(粒)模式的介紹和實戰(zhàn)使用

今天給同學們講解一下單例模式在iOS開發(fā)中的使用以及單例模式的相關優(yōu)缺點,那么廢話不多說,直接上代碼~

  • 單例模式介紹和使用場景
  • 為什么選擇單例模式?
  • 實現(xiàn)單例模式思路分析(核心&掌握
  • 通過@synchronized/dispatch_once 實現(xiàn)單例(掌握
  • 單例為什么不能通過繼承來實現(xiàn)(掌握
  • 通過宏定義來寫一個MRC/ARC環(huán)境下的單例(掌握
  • 單例模式的優(yōu)缺點(掌握
  • 單例模式誤區(qū)(了解

單例模式

  • 單例模式的作用
    可以保證在程序運行過程,一個類只有一個實例,而且該實例易于供外界訪問
    從而方便地控制了實例個數(shù)判呕,并節(jié)約系統(tǒng)資源
  • 單例模式的使用場合
    在整個應用程序中,共享一份資源(這份資源只需要創(chuàng)建初始化1次)
  • 什么時候選擇單例模式呢已卸?(重點
    • 官方說法
      一個類必須只有一個對象佛玄。客戶端必須通過一個眾所周知的入口訪問這個對象累澡。
      這個唯一的對象需要擴展的時候梦抢,只能通過子類化的方式±⒂矗客戶端的代碼能夠不需要任何修改就能夠使用擴展后的對象奥吩。
    • 個人理解
      上面的官方說法哼蛆,聽起來一頭霧水。我的理解是這樣的霞赫。
      在建模的時候腮介,如果這個東西確實只需要一個對象,多余的對象都是無意義的端衰,那么就考慮用單例模式叠洗。比如定位管理(CLLocationManager),硬件設備就只有一個旅东,弄再多的邏輯對象意義不大灭抑。

實現(xiàn)單例模式思路分析(核心

  • 1> 首先我們知道單例模式就是保障在整個應用程序中,一個類只有一個實例抵代,而我們知道創(chuàng)建對象 通過調(diào)用alloc init 方法初始化而alloc方法是用來分配內(nèi)存空間 所以我們就是攔截alloc方法保證只分配一次內(nèi)存空間腾节。(核心思路出發(fā)點
  • 2> 我們通過查閱官方Api文檔如下


    官方Api
  • 3> 那么我們就從allocWithZone方法入手但是我們?nèi)绾伪WC只創(chuàng)建一個實例對象呢?尤其在多線程的情況下荤牍,那么有同學就想到了加鎖案腺,iOS中控制多線程的方式有很多,可以使用NSLock康吵,也可以用@synchronized等各種線程同步的技術劈榨,代碼如下。(掌握
// 該類內(nèi)的全局變量涎才,外界就不能訪問和修改鞋既,變量名取_book是為了和該類的其余成員屬性區(qū)分開!牛逼的大神都這么寫 so 建議這么寫耍铜。
static ZZBook *_book;
@implementation ZZBook
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
   @synchronized(self) {
       if (_book == nil) {
           _book = [super allocWithZone:zone];
       }
   }
   return _book;
}

通過查看log我們發(fā)現(xiàn)確實做到了無論創(chuàng)建多少次都是同一個內(nèi)存地址。


ZZBook基本打印
  • 4> OC的內(nèi)部機制里有一種更加高效的方式跌前,那就是dispatch_once棕兼。性能相差好幾倍,好幾十倍抵乓。代碼如下!關于性能的比對伴挚,大神們做過實驗和分析。請參考http://blog.jimmyis.in/dispatch_once/灾炭。(掌握
// 該類內(nèi)的全局變量茎芋,外界就不能訪問和修改,變量名取_person是為了和該類的其余成員屬性區(qū)分開蜈出!牛逼的大神都這么寫 so 建議這么寫田弥。
static ZZPerson *_person;

@implementation ZZPerson

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _person = [super allocWithZone:zone];
    });
    return _person;
}
  • 5> 到這里我們實現(xiàn)了通過allocWithZone通過加鎖只分配一次內(nèi)存空間但是我們通過觀察系統(tǒng)的單例例如UIApplication / NSUserDefaults 等都會提供一個快捷的類方法訪問那么我們參照系統(tǒng)的做法,代碼如下铡原。(掌握
+ (instancetype)sharedBook
{
   @synchronized(self) {
       if (_book == nil) {
           _book = [[self alloc] init];
       }
   }
   return _book;
}
  • 6> Objective-C中構造方法不像別的語言如C++偷厦,java可以隱藏構造方法商叹,實則是公開的!由Objective-C的一些特性可以知道只泼,在對象創(chuàng)建的時候剖笙,無論是alloc還是new,都會調(diào)用到 allocWithZone方法请唱。在通過拷貝的時候創(chuàng)建對象時弥咪,會調(diào)用到-(id)copyWithZone:(NSZone *)zone,-(id)mutableCopyWithZone:(NSZone *)zone方法十绑。因此酪夷,可以重寫這些方法,讓創(chuàng)建的對象唯一孽惰。代碼如下M砹搿(掌握
//
//  ZZBook.m
//  8-多線程技術
//
//  Created by Jordan zhou on 2018/11/15.
//  Copyright ? 2018年 Jordan zhou. All rights reserved.
//

#import "ZZBook.h"

@interface ZZBook()<NSCopying,NSMutableCopying>

@end

// 該類內(nèi)的全局變量,外界就不能訪問和修改勋功,變量名取_book是為了和該類的其余成員屬性區(qū)分開坦报!牛逼的大神都這么寫 so 建議這么寫。
static ZZBook *_book;
@implementation ZZBook
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
   @synchronized(self) {
       if (_book == nil) {
           _book = [super allocWithZone:zone];
       }
   }
   return _book;
}

+ (instancetype)sharedBook
{
   @synchronized(self) {
       if (_book == nil) {
           _book = [[self alloc] init];
       }
   }
   return _book;
}

- (id)copyWithZone:(NSZone *)zone
{
   return _book;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
   return _book;
}
@end
  • 7> 補充第5點我們可以通過重寫方法狂鞋,讓創(chuàng)建的對象唯一片择,我們同樣也可以通過編譯器告訴外面,alloc骚揍,new字管,copy,mutableCopy方法不可以直接調(diào)用信不。否則編譯不過嘲叔。代碼如下!
+(instancetype) alloc __attribute__((unavailable("call sharedBook instead")));
+(instancetype) new __attribute__((unavailable("call sharedBook instead")));
-(instancetype) copy __attribute__((unavailable("call sharedBook instead")));
-(instancetype) mutableCopy __attribute__((unavailable("call sharedBook instead")));

當外部通過如上方法創(chuàng)建時會直接報錯如下


提示編譯報錯
  • 8> 通過dispatch_once實現(xiàn)單例的代碼如下3榛睢(掌握
//
//  ZZPerson.m
//  8-多線程技術
//
//  Created by Jordan zhou on 2018/11/15.
//  Copyright ? 2018年 Jordan zhou. All rights reserved.
//

#import "ZZPerson.h"

@interface ZZPerson()<NSCopying,NSMutableCopying>

@end

// 該類內(nèi)的全局變量硫戈,外界就不能訪問和修改,變量名取_person是為了和該類的其余成員屬性區(qū)分開下硕!牛逼的大神都這么寫 so 建議這么寫丁逝。
static ZZPerson *_person;

@implementation ZZPerson

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       _person = [super allocWithZone:zone];
   });
   return _person;
}

+ (instancetype)sharedPerson
{
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       _person = [[self alloc] init];
   });
   return _person;
}

- (id)copyWithZone:(NSZone *)zone
{
   return _person;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
   return _person;
}
@end
  • 9> 通過對比@synchronized或者dispatch_once實現(xiàn)單例代碼,每個類中會發(fā)現(xiàn)寫的代碼都是完全相同的 除了命名的參數(shù)不同以及方法名不一樣梭姓,如下圖霜幼!


    2種實現(xiàn)單例的對比
  • 10> 承接第9點,那么有人肯定想到那可不可以用繼承呢誉尖?我們測試通過繼承的方式打印如下代碼罪既!
#pragma mark - 測試通過繼承來實現(xiàn)單例
- (void)singleton3
{
   ZZPerson *p1 = [[ZZPerson alloc] init];
   ZZPerson *p2 = [ZZPerson sharedInstance];

   ZZBook *b1 = [[ZZBook alloc] init];
   ZZBook *b2 = [ZZBook sharedInstance];
   
   NSLog(@"%@ - %@ - %@ - %@",p1,p2,b1,b2);
}

結果如下圖: 通過打印我們發(fā)現(xiàn)書的類型也變成人了 那是因為一次性代碼在程序運行過創(chuàng)建一次ZZPerson比ZZBook先創(chuàng)建那么_instance的值永遠為ZZPerson類型所以是不能通過繼承來實現(xiàn)單例的


測試繼承來實現(xiàn)單例
  • 11> 爭取方式通過宏定義來寫以后要用直接拖走這個宏就可以!而單例模式在ARC\MRC環(huán)境下的寫法有所不同,需要編寫2套不同的代碼萝衩!可以用宏判斷是否為ARC環(huán)境回挽!
#if __has_feature(objc_arc)
// ARC
#else
// MRC
#endif

編寫代碼如下!

// name是外部傳遞的參數(shù) ##是拼接符 用來拼接參數(shù)
// .h文件
#define ZZSingletonH(name) + (instancetype)shared##name;

// 如何定義一個宏表示后面都屬于這個宏 +上" \" 即可表示后面所以的東西都屬于這個宏
// .m文件
#if __has_feature(objc_arc) // 是ARC
#define ZZSingletonM(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; \
}

#else // 不是ARC
#define ZZSingletonM(name) \
static id _instance; \
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
if (_instance == nil) { \
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; \
} \
\
- (oneway void)release \
{ \
\
} \
\
- (id)retain \
{ \
return self; \
} \
\
- (NSUInteger)retainCount \
{ \
return 1; \
} \
+ (id)copyWithZone:(struct _NSZone *)zone \
{ \
return _instance; \
} \
\
+ (id)mutableCopyWithZone:(struct _NSZone *)zone \
{ \
return _instance; \
}
#endif

單例模式的優(yōu)缺點(掌握

使用簡單猩谊、延時求值千劈、易于跨模塊

  • 內(nèi)存占用與運行時間
    對比使用單例模式和非單例模式的例子,在內(nèi)存占用與運行時間存在以下差距:
    • 單例模式:單例模式每次獲取實例時都會先進行判斷,看該實例是否存在——如果存在,則返回疑务;否則,則創(chuàng)建實例喜滨。因此,會浪費一些判斷的時間撤防。但是虽风,如果一直沒有人使用這個實例的話,那么就不會創(chuàng)建實例寄月,節(jié)約了內(nèi)存空間辜膝。
    • 非單例模式:當類加載的時候就會創(chuàng)建類的實例,不管你是否使用它漾肮。然后當每次調(diào)用的時候就不需要判斷該實例是否存在了厂抖,節(jié)省了運行的時間。但是如果該實例沒有使用的話克懊,就浪費了內(nèi)存忱辅。
  • 線程的安全性
    • 從線程的安全性上來講,不加同步的單例模式是不安全的谭溉。比如墙懂,有兩個線程,一個是線程A夜只,另外一個是線程B垒在,如果它們同時調(diào)用某一個方法,那就可能會導致并發(fā)問題扔亥。在這種情況下,會創(chuàng)建出兩個實例來谈为,也就是單例的控制在并發(fā)情況下失效了旅挤。
    • 非單例模式是線程安全的,因為程序保證只加載一次伞鲫,在加載的時候不會發(fā)生并發(fā)情況粘茄。
    • 單例模式如果要實現(xiàn)線程安全,只需要加上synchronized即可。但是這樣一來柒瓣,就會減低整個程序的訪問速度儒搭,而且每次都要判斷,比較麻煩芙贫。
    • 雙重檢查加鎖:為了解決如上的繁瑣問題搂鲫,可以使用“雙重檢查加鎖”的方式來實現(xiàn),這樣磺平,就可以既實現(xiàn)線程安全魂仍,又能使得程序性能不受太大的影響。
  • 單例模式會阻止其它對象實例化其自己的對象的副本拣挪,從而確保所有對象都訪問唯一實例擦酌。
  • 因為單例模式的類控制了實例化的過程,所以類可以更加靈活修改實例化過程菠劝。

單例模式誤區(qū)(了解

  • 內(nèi)存問題
    • 單例模式實際上延長了對象的生命周期赊舶。那么就存在內(nèi)存問題。因為這個對象在程序的整個生命都存在赶诊。所以當這個單例比較大的時候笼平,總是hold住那么多內(nèi)存,就需要考慮這件事了甫何。
    • 另外出吹,可能單例本身并不大,但是它如果強引用了另外的比較大的對象辙喂,也算是一個問題捶牢。別的對象因為單例對象不釋放而不釋放。
      當然這個問題也有一定的辦法巍耗。比如對于一些可以重新加載的對象秋麸,在需要的時候加載,用完之后炬太,單例對象就不再強引用灸蟆,從而把原先hold住的對象釋放掉。下次需要再加載回來亲族。
  • 循環(huán)依賴問題
    • 在開發(fā)過程中炒考,單例對象可能有一些屬性,一般會放在init的時候創(chuàng)建和初始化霎迫。這樣斋枢,比如如果單例A的m屬性依賴于單例B,單例B的屬性n依賴于單例A知给,初始化的時候就會出現(xiàn)死循環(huán)依賴瓤帚。死在dispatch_once里描姚。
    • 對于這種情況,最好的設計是在單例設計的時候戈次,初始化的內(nèi)容不要依賴于其他對象轩勘。如果實在要依賴,就不要讓它形成環(huán)怯邪。實在會形成環(huán)或者無法控制绊寻,就采用異步初始化的方式。先過去擎颖,內(nèi)容以后再填榛斯。內(nèi)部需要做個標識,標識這個單例在造出來之后搂捧,不能立刻使用或者完整使用驮俗。
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市允跑,隨后出現(xiàn)的幾起案子王凑,更是在濱河造成了極大的恐慌,老刑警劉巖聋丝,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件索烹,死亡現(xiàn)場離奇詭異,居然都是意外死亡弱睦,警方通過查閱死者的電腦和手機百姓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來况木,“玉大人垒拢,你說我怎么就攤上這事』鹁” “怎么了求类?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長屹耐。 經(jīng)常有香客問我尸疆,道長,這世上最難降的妖魔是什么惶岭? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任寿弱,我火速辦了婚禮,結果婚禮上按灶,老公的妹妹穿的比我還像新娘脖捻。我一直安慰自己,他們只是感情好兆衅,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般羡亩。 火紅的嫁衣襯著肌膚如雪摩疑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天畏铆,我揣著相機與錄音雷袋,去河邊找鬼。 笑死辞居,一個胖子當著我的面吹牛楷怒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓦灶,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼鸠删,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贼陶?” 一聲冷哼從身側(cè)響起刃泡,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碉怔,沒想到半個月后烘贴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡撮胧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年桨踪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芹啥。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡锻离,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叁征,到底是詐尸還是另有隱情纳账,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布捺疼,位于F島的核電站疏虫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏啤呼。R本人自食惡果不足惜卧秘,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望官扣。 院中可真熱鬧翅敌,春花似錦、人聲如沸惕蹄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遭顶,卻和暖如春张峰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棒旗。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工喘批, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铣揉。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓饶深,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逛拱。 傳聞我的和親對象是個殘疾皇子敌厘,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結構(3).初始化時...
    歐辰_OSR閱讀 29,321評論 8 265
  • 單例模式(SingletonPattern)一般被認為是最簡單、最易理解的設計模式橘券,也因為它的簡潔易懂额湘,是項目中最...
    成熱了閱讀 4,227評論 4 34
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,089評論 1 32
  • 設計模式概述 在學習面向?qū)ο笃叽笤O計原則時需要注意以下幾點:a) 高內(nèi)聚、低耦合和單一職能的“沖突”實際上旁舰,這兩者...
    彥幀閱讀 3,734評論 0 14
  • 上至達官貴人锋华,下至販夫走卒,人生最難熬的就是陪孩子高三那一年箭窜,輕不得重不得毯焕,急不得慢不得。在這一年里磺樱,父母仿佛經(jīng)歷...
    青衫居士李君山閱讀 3,688評論 2 4