單例模式大概是設(shè)計模式種較簡單的一種設(shè)計模式。但在實際的開發(fā)過程中仍然存在一些坑。所以本文總結(jié)了下iOS中的單例模式。
什么是單例模式?
- ensures a class only has one instance
- provides a global point of access to it
- 確保一個類永遠(yuǎn)只有一個實例
- 提供一個全局的訪問入口訪問這個實例
蘋果官方文檔的一副圖描述了請求普通類和單例的區(qū)別:

如何實現(xiàn)基本的單例模式?
Singleton *sharedInstance = nil;
+ (instancetype)sharedIntance {
if (sharedInstance == nil) {
sharedInstance = [[Singleton alloc] init];
}
return sharedInstance;
}
全局的變量sharedInstance有個缺點,可以被外部隨意修改,為了隔離外部修改,可以設(shè)置成局部靜態(tài)變量。
+ (instancetype)sharedInstance {
static Singleton *sharedInstance = nil;
if (sharedInstance == nil) {
sharedInstance = [[Singleton alloc] init];
}
return sharedInstance;
}
單例的核心思想就算實現(xiàn)了。
多線程如何處理?
上述例子雖然實現(xiàn)了單例的核心思想,但依然存在問題。在多線程情況下即多個線程同時訪問sharedInstance工廠方法,并不能保證只創(chuàng)建一個實例對象。
那么,如何保證在多線程的下依舊能夠只創(chuàng)建一個實例對象呢?iOS下我們可以使用NSLock、@synchronized等多種線程同步技術(shù)。
+ (instancetype)sharedInstance {
static Singleton *sharedInstance = nil;
@synchronized (self) {
if (sharedInstance == nil) {
sharedInstance = [[Singleton alloc] init];
}
}
return sharedInstance;
}
@synchronized雖然保證了在多線程下調(diào)用sharedInstance工廠方法只會創(chuàng)建一個實例對象,但是@synchronized的性能較差。OC內(nèi)部提供了一種更加高效的方式,那就是dispatch_once。@synchronized性能相較于dispatch_once要差幾倍,甚至幾十倍。關(guān)于二者的性能對比,請參考這里
+ (instancetype)sharedInstance {
static Singleton *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[Singleton alloc] init];
});
return sharedInstance;
}
Objective-C中實現(xiàn)單例存在的坑
上述實現(xiàn)單例的方式看起來很完美了。雖然我們提供了一個方便的工廠方法返回單例,但是用戶依然能夠調(diào)用alloc方法創(chuàng)建對象。這樣外部使用的時候依舊能夠創(chuàng)建多個實例。解決上述問題有兩種方案:
1. 技術(shù)上實現(xiàn)無論怎么調(diào)用都返回同一個單例對象
To create a singleton as the sole allowable instance of a class in the current process. This code does the following:
- It declares a static instance of your singleton object and initializes it to
nil.- In your class factory method for the class (named something like “sharedInstance” or “sharedManager”), it generates an instance of the class but only if the static instance is
nil.- It overrides the
allocWithZone:method to ensure that another instance is not allocated if someone tries to allocate and initialize an instance of your class directly instead of using the class factory method. Instead, it just returns the shared object.- It implements the base protocol methods
copyWithZone:,release,retain,retainCount, andautoreleaseto do the appropriate things to ensure singleton status. (The last four of these methods apply to memory-managed code, not to garbage-collected code.)
+ (instancetype)sharedInstance {
static Singleton *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[super allocWithZone:NULL] init];
});
return sharedInstance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
- (id)copy {
return [Singleton sharedInstance];
}
- (id)mutableCopy {
return [Singleton sharedInstance];
}
如下圖所示,不管使用何種方式創(chuàng)建對象都返回相同的實例對象。

2.利用編譯器特性給用戶提示,但不強制約束
利用編譯器特性直接告訴外部new、alloc 、copy、mutableCopy方法不能直接調(diào)用,否則編譯不通過。
+ (instancetype)sharedInstance;
+ (instancetype)new OBJC_UNAVAILABLE("use sharedInstance instead.");
+ (instancetype)alloc OBJC_UNAVAILABLE("use sharedInstance instead.");
- (id)copy OBJC_UNAVAILABLE("use sharedInstance instead.");
- (id)mutableCopy OBJC_UNAVAILABLE("use sharedInstance instead.");
若直接調(diào)用alloc等方法創(chuàng)建對象,編譯器則會給出錯誤提示:

單例模式潛在的問題
- 內(nèi)存問題
單例模式實際上是延長了對象的生命周期,那么就存在內(nèi)存的問題,因為單例對象在程序的整個生命周期里都存在,直到程序退出才會釋放。
參考文獻(xiàn):
Singleton
Creating a Singleton Instance