一、概念
1、單例模式的動機
? 實際開發(fā)中,為了節(jié)約系統(tǒng)資源,并且共享一份數(shù)據(jù),有時需要確保系統(tǒng)中某個類只有唯一一個實例,當(dāng)這個唯一實例創(chuàng)建成功之后,我們無法再創(chuàng)建一個同類型的其他對象,所有的操作都只能基于這個唯一實例。為了確保對象的唯一性,我們可以通過單例模式來實現(xiàn),這就是單例模式的動機所在。
2、單例模式的定義
? 單例模式(Singleton Pattern):確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例,這個類稱為單例類,它提供全局訪問的方法。單例模式是一種對象創(chuàng)建型模式。
? 單例模式有三個要點:一是某個類只能有一個實例;二是它必須自行創(chuàng)建這個實例;三是它必須自行向整個系統(tǒng)提供這個實例。
3、餓漢式單例與懶漢式單例
餓漢式單例:當(dāng)類被系統(tǒng)加載時,靜態(tài)變量instance會被初始化,單例類的唯一實例將被創(chuàng)建。
懶漢式單例:在類第一次調(diào)用的時候,instance被初始化,單例類的唯一實例將被創(chuàng)建。
4、結(jié)構(gòu)圖
? 這里只放一個普遍的單例模式結(jié)構(gòu)圖,餓漢式單例與懶漢式單例區(qū)別就是靜態(tài)變量instance初始化的時間不同。

二、示例
1、餓漢式單例代碼
? 根據(jù)餓漢式單例的定義,在類被加載的時候就對靜態(tài)instance初始化,Java代碼比較簡單:
Class Singleton {
private static final Singleton instance = new Singleton(); //保證實例唯一
private Singleton() { } //私有初始化方法
public static Singleton getInstance() { //利用工廠模式統(tǒng)一提供對外方法
return instance;
}
}
在iOS開發(fā)中,餓漢式單例代碼略顯奇怪:
// .h文件
@interface Singleton : NSObject
+ (instancetype)sharedInstance;
@end
// .m文件
static Singleton *instance; //聲明靜態(tài)變量
@implementation Singleton
+ (void)load { // 在類被加載的時候會調(diào)用一次load方法,這個時候進(jìn)行初始化
instance = [[super allocWithZone:NULL] init];
}
+ (instancetype)sharedInstance { // 統(tǒng)一對外方法
return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone { //就算通過alloc創(chuàng)建的也是同一個instance
return instance;
}
@end
2、懶漢式單例代碼
? 根據(jù)懶漢式單例定義,在類第一次調(diào)用的時候?qū)o態(tài)instance進(jìn)行初始化,Java代碼簡單:
class LazySingleton {
private static LazySingleton instance = null; //加載的時候不初始化
private LazySingleton() { } //私有初始化方法
public static LazySingleton getInstance() { //第一次調(diào)用初始化,synchronized進(jìn)行線程同步
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
}
在iOS開發(fā)中,懶漢式單例代碼如下:
// .h文件
@interface Singleton : NSObject
+ (instancetype)sharedInstance;
@end
// .m文件
@implementation Singleton
+ (instancetype)sharedInstance {
return [[self alloc] init]; //alloc最終會調(diào)用allocWithZone方法
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static Singleton *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ //dispatch_once里只會調(diào)用一次,是線程安全的
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
@end
運行代碼:
- (void)viewDidLoad {
[super viewDidLoad];
Singleton *s1 = [Singleton sharedInstance];
Singleton *s2 = [[Singleton alloc] init];
NSLog(@"s1 = %p", s1);
NSLog(@"s2 = %p", s2);
}
打印結(jié)果:s1與s2打印地址是一樣的,是同一個實例。
s1 = 0x60000380c060
s2 = 0x60000380c060
3、iOS項目中常見用法
? 在iOS實際開發(fā)中,經(jīng)常是使用另外一種方式創(chuàng)建單例模式,嚴(yán)格來說這種做法不嚴(yán)謹(jǐn),但是代碼簡單方便,所以使用還是廣泛。
@interface Singleton : NSObject
+ (instancetype)sharedInstance;
@end
@implementation Singleton
+ (instancetype)sharedInstance {
static Singleton *instance; //聲明靜態(tài)變量
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ //第一次調(diào)用的時候初始化一次
instance = [Singleton new];
});
return instance; //返回靜態(tài)變量
}
@end
這種做法,如果通過alloc創(chuàng)建的實例會不一樣。
運行代碼:
- (void)viewDidLoad {
[super viewDidLoad];
Singleton *s1 = [Singleton sharedInstance];
Singleton *s2 = [[Singleton alloc] init];
NSLog(@"s1 = %p", s1);
NSLog(@"s2 = %p", s2);
}
打印結(jié)果:s1和s2指針地址不同,是不同的instance。
s1 = 0x600001e58ed0
s2 = 0x600001e58f40
三、總結(jié)
? 單例模式作為一種目標(biāo)明確、結(jié)構(gòu)簡單、理解容易的設(shè)計模式,在軟件開發(fā)中使用頻率相當(dāng)高。
1、優(yōu)點
1、單例模式提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴(yán)格控制客戶怎樣以及何時訪問它。
2、由于在系統(tǒng)內(nèi)存中只存在一個對象,因此可以節(jié)約系統(tǒng)資源,對于一些需要頻繁創(chuàng)建和銷毀的對象單例模式無疑可以提高系統(tǒng)的性能。
2、缺點
1、由于單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
2、單例類的職責(zé)過重,在一定程度上違背了“單一職責(zé)原則”。因為單例類既充當(dāng)了工廠角色,提供了工廠方法,同時又充當(dāng)了產(chǎn)品角色,包含一些業(yè)務(wù)方法,將產(chǎn)品的創(chuàng)建和產(chǎn)品的本身的功能融合到一起。
3、現(xiàn)在很多面向?qū)ο笳Z言(如Java、C#)的運行環(huán)境都提供了自動垃圾回收的技術(shù),因此,如果實例化的共享對象長時間不被利用,系統(tǒng)會認(rèn)為它是垃圾,會自動銷毀并回收資源,下次利用時又將重新實例化,這將導(dǎo)致共享的單例對象狀態(tài)的丟失。
3、適用場景
1、系統(tǒng)只需要一個實例對象。
2、客戶調(diào)用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。
4、iOS應(yīng)用舉例
? 比如UIApplication就是一個單例類,提供全局的應(yīng)用服務(wù),比如NSFileManager文件管理類。
Demo地址:iOS-Design-Patterns