一. ARC環(huán)境下的單例模式
-
單例模式的基本概念
- 單例, 顧名思義, 即在整個程序中, 某一個類只有唯一一個實例, 他貫穿整個應用程序, 不會被創(chuàng)建第二次, 分配第二次內存.
- 對于單例, 較多的都是應用于一些工具類, 可以起到特定作用并且功能比較常用的類, 就可以制作為單例模式.
-
在ARC環(huán)境下的單例模式
-
在類的內部提供一個static修飾的全局變量
- 使用static修飾的變量, 它的生命周期會延長為整個程序結束的時候才被銷毀
- 并且這個變量只會生成一份內存, 只會初始化一次
-
提供一個類方法, 方便外界訪問
- 這一點建議遵循蘋果的命名習慣:
shareXXXX
- 這一點建議遵循蘋果的命名習慣:
重寫
+allocWithZone
方法, 保證永遠都只為單例對象分配一次內存空間為了嚴謹, 可以重寫
-copyWithZone
和-mutableCopyWithZone
方法-
單例代碼有兩種不同的方式
- GCD的一次性代碼:
dispatch_once
- 使用的比較多, 保證block中的代碼, 在整個程序中只運行一次
- 由于單例對象是一個靜態(tài)變量, 程序結束才釋放, 所以使用一次性代碼初始化后, 他會一直存在, 并通過返回單例對象的方法隨時可以獲取
- GCD線程鎖:
@synchronized
- 此方法常用于多線程, 并且這個單例對象會頻繁被子線程使用的時候調用
- 建議使用一次性代碼的單例創(chuàng)建放放
- GCD的一次性代碼:
-
代碼示例:
// 1. 創(chuàng)建靜態(tài)變量 static FHTool * _instance; // 2. 重寫allocWithZone方法,確保內存值分配一次并且每次返回的都是同一個實例 //+ (instancetype)allocWithZone:(struct _NSZone *)zone { // // // 2.1 為了防止多個線程同時使用該方法,避免訪問同一塊內存出現(xiàn)問題,因此要加上互斥鎖 // @synchronized(self) { // if (_instance == nil) { // _instance = [super allocWithZone:zone]; // } // } // return _instance; //} // 3. 使用GCD一次性代碼 + (instancetype)allocWithZone:(struct _NSZone *)zone { // 3.1 一次性代碼保證全局只執(zhí)行一次, 之后永遠返回靜態(tài)變量_instance static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); return _instance; } + (instancetype)shareFHTool { // alloc方法會在底層自動調用allocWithZone方法 return [[self alloc] init]; } - (id)copy { return _instance; } - (id)mutableCopy { return _instance; }
-
二. MRC環(huán)境下的單例模式
與ARC相比, 目前使用MRC的項目基本也就是一些大公司的老項目, 所以建議只作為拓展知識理解一下
-
MRC單例的步驟
- 在類的內部提供一個static修飾的全局變量
- 提供一個類方法方便外界調用:
shareXXX
- 重寫
+allocWithZone
方法, 保證永遠只分配一次內存 - 謹慎起見, 可以重寫
-copyWithZone
和-mutableCopyWithZone
方法, 每次都返回單例對象 - 重寫
-release
方法, 單例對象不能被釋放掉 - 重寫
-retain
方法, 單例對象不需要被多次引用, 每次retain只返回單例對象即可 - 建議重寫
-retainCount
方法, 隨意給一個最大值, 提醒外界這是一個單例方法
-
注意點:
- 在這里可以使用一個條件編譯, 寫一份單例代碼, 可以同時供ARC和MRC一起使用
-
#if __has_feature(objc_arc) #else
使用這個條件編譯- 如果當前是ARC的話, 就不需要重寫
-retain
等MRC方法 - 如果是MRC的話, 再通過條件編譯將
MRC內存管理方法
加入編譯
- 如果當前是ARC的話, 就不需要重寫
-
代碼示例:
//1. 創(chuàng)建靜態(tài)變量 static FHTool *_instance; + (instancetype)shareFHTool { return [[FHTool alloc] init]; } // 2. 重寫實例化方法 + (instancetype)allocWithZone:(struct _NSZone *)zone { // @synchronized(self) { // if (_instance == nil) { // _instance = [super allocWithZone:zone]; // } // } static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); return _instance; } - (id)copy { return _instance; } - (id)mutableCopy { return _instance; } #if __has_feature(objc_arc) #else // 重寫release,讓單例對象不能被release - (oneway void)release { } // 重寫retain,單例不需要修改引用計數(shù) - (instancetype)retain { return _instance; } // 查詢當前引用計數(shù),返回最大值即可 - (NSUInteger)retainCount { return MAXFLOAT; } #endif
三. 將單例模式封裝為宏
可以使用宏, 來講單例模式的代碼封裝到一個PCH全局性宏文件中
但是不推薦這樣使用, 按照蘋果的建議, 在程序中應該盡量少用PCH文件
對于一個程序來說, 單例并不會有很多, 因此沒有太大的必要給單例代碼定義一個全局宏
-
因此代碼僅作為了解
#ifdef __OBJC__ // H文件 #define SingleH(name) +(instancetype)share##name; #if __has_feature(objc_arc) // M文件,ARC #define SingleM(name) static id _instance;\ + (instancetype)share##name {\ return [[self alloc] init];\ }\ + (instancetype)alloc {\ static dispatch_once_t onceToken;\ dispatch_once(&onceToken, ^{\ _instance = [super alloc];\ });\ return _instance;\ } #else // M文件,MRC #define SingleM(name) static id _instance;\ + (instancetype)share##name {\ return [[self alloc] init];\ }\ + (instancetype)alloc {\ static dispatch_once_t onceToken;\ dispatch_once(&onceToken, ^{\ _instance = [super alloc];\ });\ return _instance;\ }\ - (id)copy {\ return _instance;\ }\ - (id)mutableCopy {\ return _instance;\ } #endif #endif