之前在 實現(xiàn)Singleton 模式——七種實現(xiàn)方式中發(fā)現(xiàn)java 的單例有七種實現(xiàn)方式,對里面的懶漢和餓漢模式稍微研究了下,發(fā)現(xiàn)IOS 里面也可以對應(yīng)實現(xiàn)。
簡述
面向?qū)ο髴?yīng)用程序中的單例類(singleton class)總是返回自己的同一個實例。它提供了對象所提供的資源的全局訪問點。與這類設(shè)計相關(guān)的設(shè)計模式稱為單例模式。
大家在開發(fā)過程中也見過不少的單例,比如UIApplication、UIAccelerometer(重力加速)、NSUserDefaults、NSNotificationCenter,當(dāng)然,這些是開發(fā)Cocoa Touch框架中的,在Cocoa框架中還有NSFileManager、NSBundle等。
1、懶漢模式:實現(xiàn)原理和懶加載其實很像,如果在程序中不使用這個對象,那么就不會創(chuàng)建,只有在你使用代碼創(chuàng)建這個對象,才會創(chuàng)建。這種實現(xiàn)思想或者說是原理都是iOS開發(fā)中非常重要的,所以,懶漢式的單例模式也是最為重要的,是開發(fā)中最常見的。
2、餓漢模式:在沒有使用代碼去創(chuàng)建對象之前,這個對象已經(jīng)加載好了,并且分配了內(nèi)存空間,當(dāng)你去使用代碼創(chuàng)建的時候,實際上只是將這個原本創(chuàng)建好的對象拿出來而已。
3.使用GCD代替手動鎖實現(xiàn)單例模式
4.使用宏封裝直接便于開發(fā)使用
talk is cheap, show me the code 直接上代碼展示
1.懶漢模式
static id instance = nil;
// 懶加載 線程不安全 單例
+ (instancetype) ShareInstance
{
if (instance == nil) {
instance = [[self alloc] init];
}
return instance;
}
// 懶加載 加鎖 單例
+ (instancetype) ShareInstance1
{
@synchronized (self) { //為了線程安全,加上互斥鎖
if (instance == nil) {
instance = [[self alloc] init];
}
}
return instance;
}
需要的注意點:
1)加synchronized 是為了保證單例的讀取線程安全,為什么需要添加synchronized 我已經(jīng)在之前的文章中 IOS nonatomic 與atomic 分析 描述過此類問題,有趣的是我在網(wǎng)上看到有朋友問:
+(instancetype)sharedSingleton{
static id instance = nil;
if (!instance) {
@synchronized (self) {
instance = [[self alloc] init];
}
}
return instance;
}
這樣行嗎?
答案是肯定不行的,稍微對synchronized 有點了解就知道這種只是“鎖”住了對象的創(chuàng)建,沒有“鎖”住 if 判斷。如果兩個線程都進到了 if 里面,一樣可以生成兩個對象。
2)static
修飾局部變量:修飾了局部變量的話,那么這個局部變量的生命周期就和不加static的全局變量一樣了(也就是只有一塊內(nèi)存區(qū)域,無論這個方法執(zhí)行多少次,都不會進行內(nèi)存的分配),不同的在于作用域仍然沒有改變
修飾全局變量:
如果不適用static的全局變量,我們可以在其他的類中使用extern關(guān)鍵字直接獲取到這個對象,可想而知,在我們所做的單例模式中,如果在其他類中利用extern拿到了這個對象,進行一個對象銷毀,例如:
extern id instance;
instance = nil;
這時候在這句代碼之前創(chuàng)建的單例就銷毀了,再次創(chuàng)建的對象就不是同一個了,這樣就無法保證單例的存在,所以對于全局變量的定義,需要加上static修飾符
- allocWithZone 與copyWithZone方法
我們在項目中一般是直接調(diào)用自己定義的類方法:ShareInstance,但是有時候也會調(diào)用alloc方法直接對單例進行初始化,那么也會導(dǎo)致沒有產(chǎn)生該單例,所以我們需要保證應(yīng)用中只有一個該類的對象需要重寫它的allocWithZone 方法,alloc調(diào)用的底層也是allocWithZone方法,直接與上述的單例方法類同。
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 解決多線程問題
@synchronized(self){
if (instance == nil) {
// 調(diào)用super的allocWithZone方法來分配內(nèi)存空間
instance = [super allocWithZone:zone];
}
}
return instance;
}
如果使用copy創(chuàng)建出新的對象的話,那么就不能夠保證單例的存在了也會導(dǎo)致同樣的問題。此處直接返回instance就可以了。
- (id)copyWithZone:(NSZone *)zone
{
return instance;
}
2.餓漢模式
在沒有使用代碼去創(chuàng)建對象之前,這個對象已經(jīng)加載好了,并且分配了內(nèi)存空間,當(dāng)你去使用代碼創(chuàng)建的時候,實際上只是將這個原本創(chuàng)建好的對象拿出來而已。
在alloc之前如何將對象直接賦值呢,有兩種方式:load和initialize。具體對于兩種方法的描述已經(jīng)有很多人描述過了,詳見: iOS類方法load和initialize詳解
大致上就是:
load 會在類加載到運行環(huán)境中的時候就會調(diào)用且僅調(diào)用一次,同時注意一個類只會加載一次(類加載有別于引用類,可以這么說,所有類都會在程序啟動的時候加載一次,不管有沒有在目前顯示的視圖類中引用到)
initialize方法:當(dāng)?shù)谝淮问褂妙惖臅r候加載且僅加載一次
static id instance = nil;
+ (void)load
{
instance = [[self alloc]init];
}
+ (void)initialize
{
instance = [[self alloc]init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
if (instance == nil) {
instance = [super allocWithZone:zone];
}
return instance;
}
+ (instancetype)sharedInstance
{
return instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return instance;
}
實際上只需要實現(xiàn) load 與 initialize 其中一種即可實現(xiàn)單例。
3.使用GCD代替手動鎖實現(xiàn)單例模式(推薦使用)
這個在所有的使用者中是最多的,我們使用dispatch_once 方法實現(xiàn)單例模式。
代碼如下:
static id instance = nil;
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];
});
return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];
});
return instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return instance;
}
為什么推薦使用GCD代替手動鎖實現(xiàn)單例模式 :
1.寫法簡單,比起需要手動加鎖簡單很多。
2.性能優(yōu)異: @synchronized采用的是遞歸互斥鎖來實現(xiàn)線程安全,而dispatch_once的內(nèi)部則使用了很多原子操作來替代鎖,以及通過信號量來實現(xiàn)線程同步,而且有很多針對處理器優(yōu)化的地方。
此處有專門的文章細說GCD的優(yōu)異:
細說@synchronized和dispatch_once
簡單的來說:
就是@synchronized 在多線程中加鎖,其他線程是等待的,造成了線程資源浪費。
而 dispatch_once主要是根據(jù)onceToken的值來決定怎么去執(zhí)行代碼。
1.當(dāng)onceToken = 0時,線程執(zhí)行dispatch_once的block中代碼
2.當(dāng)onceToken = -1時,線程跳過dispatch_once的block中代碼不執(zhí)行
3.當(dāng)onceToken為其他值時,線程被阻塞,等待onceToken值改變
當(dāng)線程調(diào)用shareInstance,此時onceToken = 0,調(diào)用block中的代碼,此時onceToken的值變?yōu)?40734537148864。當(dāng)其他線程再調(diào)用shareInstance方法時,onceToken的值已經(jīng)是140734537148864了,線程阻塞。當(dāng)block線程執(zhí)行完block之后,onceToken變?yōu)?1.其他線程不再阻塞,跳過block。下次再調(diào)用shareInstance時,block已經(jīng)為-1.直接跳過block。
4.使用宏封裝直接便于開發(fā)使用
這邊就只是簡單的告訴你可以通過宏封裝單例達到方便直接使用單例。
// .h文件的代碼
#define NTSingletonH(name) + (instancetype)shared##name;
// .m文件中的代碼(使用條件編譯來區(qū)別ARC和MRC)
#if __has_feature(objc_arc)
#define NTSingletonM(name)\
static id instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[super alloc]init];\
});\
return instance;\
}\
+ (instancetype)shared##name\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[self alloc]init];\
});\
return instance;\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return instance;\
}
#else
#define NTSingletonM(name)\
static id instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[super alloc]init];\
});\
return instance;\
}\
+ (instancetype)shared##name\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[self alloc]init];\
});\
return instance;\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return instance;\
}\
- (oneway void)release\
{\
}\
- (instancetype)retain\
{\
return instance;\
}\
- (NSUInteger)retainCount\
{\
return 1;\
}\
- (instancetype)autorelease\
{\
return instance;\
}
#endif
使用方式就是在新類 NewSingleton 中
@interface NewSingleton : NSObject
NTSingletonH(Manager)
@end
@implementation NewSingleton
NTSingletonM(Manager)
@end
需要的時候簡單調(diào)用 [NewSingleton sharedManager] 即可