什么是單例模式
- 單例模式就是要保證系統中一個類只有一個對象實例。無論用什么方法創建多少次,所得的對象都是同一個對象。
單例模式的應用場景
- 在iOS開發中,我們已經遇到過很多單例模式的身影:
- [UIApplication sharedApplication]、[NSUserDefaults standardUserDefaults]、[NSNotificationCenter defaultCenter] 等等。
- 音樂播放器中用于播放音樂的播放器對象、一個APP中用于保存并且能夠隨時方便地獲取的用戶信息的對象 等等。
單例模式的關鍵
對象只創建一次
可供全局訪問
不會被釋放,直至程序結束
單例模式的分析與實現
- 對象只創建一次:
在iOS中我們創建一個對象一般用:alloc init 或 new,其中new方法內部實際也是通過alloc init創建的,所以我們把注意力放在alloc init上。首先alloc方法是給對象分配內存空間,然后init方法是對該對象進行初始化,所以想要控制對象的創建關鍵是在alloc方法上,又由于alloc默認是調用allocWithZone方法,所以我們應該重寫allocWithZone方法來控制對象只能創建一次:
id instance; // 定義全局變量來保存單例對象,此處還不完善,后面還會提到
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
// 此處if判斷可以避免頻繁加鎖,只要對象已創建就直接返回,亦相當于懶加載
if (instance == nil) {
// 方法一:互斥鎖方式
@synchronized(self) {
if (instance == nil) {
instance = [super allocWithZone:zone]; // 用super去調用,避免死循環
}
}
// 方法二:GCD一次性代碼方式
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone]; // 用super去調用,避免死循環
});
}
return instance;
}
-
可供全局訪問:
單例模式要提供一個類方法來獲取單例對象,例如:Tools *tools = [Tools sharedTools]; UserTool *userTool = [UserTool defaultUserTool];
實現如下:
// 單例類方法 命名規則: shared + 類名 或 default + 類名 + (instancetype)sharedTools { if (instance == nil) { instance = [self alloc] init]; // 最終還是調用allocWithZone方法 } return instance; }
-
不會被釋放,直至程序結束:
在第一個關鍵點中,我們定義了一個全局變量id instance;
來保存我們創建的單例對象,但是有個弊端,如果在別的文件中(別的類)使用extern關鍵字來獲取這個對象是可以拿到的,并且可以把該對象銷毀,例如:extern id instance; instance = nil;
這樣以來,下次再獲取單例對象的時候發現為nil就會重新創建對象,即二次創建對象,亦即不為單例模式,為了防止單例對象的銷毀,我們應該使用static修飾用于保存單例對象的變量,限制變量的作用域為此文件可用,那么別的文件(別的類)就無法拿到這個對象,從而達到單例對象不會被釋放。
即把id instance;
改為static id instance;
嚴謹的單例模式
-
創建對象除了alloc init 和 new 以外,還可以通過copy 和 mutableCopy來創建對象,為了嚴謹起見,我們還需要控制這兩個方法的創建過程,即需要重寫copyWithZone和mutableCopyWithZone方法,重寫這兩個方法需要分別遵守NSCopying 和 NSMutableCopying協議。
因為這兩個方法是對象方法,所以當想要使用這兩個方法來創建新對象的時候,只能是用單例對象來調用此方法,即單例對象已經創建了,所以我們只需要返回我們保存的單例對象即可,代碼如下:- (id)copyWithZone:(NSZone *)zone { return instance; } - (id)mutableCopyWithZone:(NSZone *)zone { return instance; }
至此一個嚴謹的單例設計模式已經完成了,下面附上完整代碼:
#import "Tools.h"
@implementation Tools
static id instance;
+ (instancetype)sharedTools {
if (instance == nil) {
instance = [[self alloc] init];
}
return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
// 此處if判斷可以避免頻繁加鎖,只要對象已創建就直接返回,亦相當于懶加載
if (instance == nil) {
// 方法一:互斥鎖方式
@synchronized(self) {
if (instance == nil) {
instance = [super allocWithZone:zone]; // 用super去調用,避免死循環
}
}
// 方法二:GCD一次性代碼方式
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone]; // 用super去調用,避免死循環
});
}
return instance;
}
// 遵守NSCopying協議
- (id)copyWithZone:(NSZone *)zone {
return instance;
}
// 遵守NSMutableCopying協議
- (id)mutableCopyWithZone:(NSZone *)zone {
return instance;
}
@end