iOS中單例詳解
分享是每個優秀的程式設計師所必備的品質
iOS單例是設計模式的一種,iOS系統中存在很多這種模式。比如UIApplication、NSNotificationCenter、NSUserDefaults等等。
單例模式的作用
- 可以保證在程式執行過程,一個類只有一個例項,而且該例項易於供外界訪問
- 控制了例項物件的個數,節約系統資源
- 在整個應用程式中,共享一份資源(這份資源只需要建立初始化1次)
ARC實現單例
ARC模式下,實現單例的步驟:
share+類名|default + 類名 | share | default | 類名
直接上程式碼:
.h檔案中
// 提供一個類方法,方便外界訪問 +(instancetype)shareTool;
.m檔案中
//提供一個static修飾的全域性變數,強引用著已經例項化的單例物件例項 static RCTools *_instance; //類方法,返回一個單例物件 + (instancetype)shareTool{ //注意:這裡建議使用self,而不是直接使用類名Tools(考慮使用巨集定義來繼承) return [[self alloc]init]; } // 重寫+allocWithZone方法,alloc方法會呼叫此方法,提供兩種方法 + (instancetype)allocWithZone:(struct _NSZone *)zone { // 第一種方法使用GCD中的一次性程式碼 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); /* //第二種方法使用@synchronized加互斥鎖的方式 @synchronized(self) { if (_instance == nil) { _instance = [super allocWithZone:zone]; } } */ return _instance; } /* 1. mutableCopy 建立一個新的可變物件,並初始化為原物件的值,新物件的引用計數為 1; 2. copy 返回一個不可變物件。分兩種情況:(1)若原物件是不可變物件,那麼返回原物件,並將其引用計數加 1 ;(2)若原物件是可變物件,那麼建立一個新的不可變物件,並初始化為原物件的值,新物件的引用計數為 1。 */ //讓程式碼更加的嚴謹,重寫下面兩種方法(程式碼不提示可匯入<NSCopying, NSMutableCopying>協議) - (nonnull id)copyWithZone:(nullable NSZone *)zone { //return [[self class] allocWithZone:zone]; return _instance; } - (nonnull id)mutableCopyWithZone:(nullable NSZone *)zone { return _instance; }
MRC實現單例
MRC模式下,實現單例的步驟,和ARC基本差不多,多重寫了幾個方法:
- 在類的內部提供一個static修飾的全域性變數
- 提供一個類方法,方便外界訪問
- 重寫+allocWithZone方法,保證永遠都只為單例物件分配一次記憶體空間
- 嚴謹起見,重寫-copyWithZone方法和-MutableCopyWithZone方法
- 重寫release方法,(單例類本意是全域性持有的不被釋放的,應用程式結束時才會釋放,如果不重寫release方法,單列類在全域性中只能被release一次,再次呼叫release方法會崩潰)
- 重寫retain方法,保證引用計數不會一直增加
- 建議在retainCount方法中返回一個最大值
配置MRC環境知識
- 注意ARC不是垃圾回收機制,是編譯器特性
- 配置MRC環境:build setting ->搜尋automatic ref->修改為NO
相關程式碼
.h檔案程式碼和ARC模式下相同
.m檔案程式碼
static RCTools *_instance; + (instancetype)shareTool{ return [[self alloc]init]; } + (instancetype)allocWithZone:(struct _NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); return _instance; } - (nonnull id)copyWithZone:(nullable NSZone *)zone { return _instance; } - (nonnull id)mutableCopyWithZone:(nullable NSZone *)zone { return _instance; } //在MRC環境下,如果使用者retain了一次,那麼直接返回instance變數,不對引用計數器+1 //如果使用者release了一次,那麼什麼都不做,因為單例模式在整個程式執行過程中都擁有且只有一份,程式退出之後被釋放,所以不需要對引用計數器操作 - (oneway void)release { } - (instancetype)retain{ return _instance; } //習慣用法,有經驗的程式設計師通過列印retainCount這個值可以猜到這是一個單例 - (NSUInteger)retainCount { return MAXFLOAT; }
封裝一個通用的巨集定義版本
單例類是不可以使用繼承的,舉個例子:
寫一個單例類X作為父類,A、B、C都是繼承於X的單例類,X、A、B、C按次序各自初始化後列印物件,會發現都是X類的物件。
這裡強調一下並不是說所有繼承的子類的物件都跟父類一樣,只是誰最先初始化物件,後面所有的類初始化出的物件都跟它一樣,跟父類或子類無關,只跟初始化的順序有關。
ARC和MRC環境下都適用
使用條件編譯來判斷當前專案環境是ARC還是MRC
#if __has_feature(objc_arc) //如果是ARC,那麼就執行這裡的程式碼1 #else //如果不是ARC,那麼就執行代理的程式碼2 #endif
單例是不可以用繼承的,如果就想一次寫可以多次使用,推薦使用帶引數的巨集定義!
巨集定義使用##拼接字串
程式碼:
#define SingleH(name) +(instancetype)share##name; #if __has_feature(objc_arc) //條件滿足 ARC #define SingleM(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];\ }\ \ -(nonnull id)copyWithZone:(NSZone *)zone{\ return _instance;\ }\ \ -(nonnull id)mutableCopyWithZone:(NSZone *)zone{\ return _instance;\ } #else //MRC #define SingleM(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];\ }\ \ -(nonnull id)copyWithZone:(NSZone *)zone{\ return _instance;\ }\ \ -(nonnull id)mutableCopyWithZone:(NSZone *)zone{\ return _instance;\ }\ -(oneway void)release{\ }\ \ -(instancetype)retain{\ return _instance;\ }\ \ -(NSUInteger)retainCoun{\ return MAXFLOAT;\ } #endif
小夥伴可以直接複製上述巨集定義直接使用

.h檔案中.png

.m檔案中.png
因為最近有小夥伴找我詢問關於iOS多執行緒方面的知識,我怕誤人子弟,有遺漏的地方就把以前的東西整理一下,希望可以幫助更多的人!