前言
單例應(yīng)該是iOS中很簡單的設(shè)計模式,寫個單例很簡單很方便。網(wǎng)上例子也很多,大家也是基本上copy下來就可以了,但是要知其所以然這個問題的文章就很少。所以我在這寫一下好的單例,以及為什么這樣么寫。
創(chuàng)建單例的幾種方式
一、單線程模式單例
+ (instancetype)sharedInsance{
static Singleton *singleton = nil;
if (!singleton ) {
singleton = [[Singleton alloc] init];
}
return singleton;
}
- 單線程單例只有在單個線程使用的情況下實用,在多線程的情況下,會產(chǎn)生線程不安全的情況。
- 我們還需要把a(bǔ)lloc方法變?yōu)樗接蟹椒ú判?,?yán)格的單例是不允許再創(chuàng)建其他實例的,而alloc方法可以在外部任意生成實例
- 兩條線程里同時調(diào)用sharedLoadData方法,沒有鎖的話可能會產(chǎn)生兩個singleton實例,這樣單例就失去意義了。
二、多線程加鎖單例
+ (instancetype)sharedInsance {
static Singleton *singleton;
@synchronized (self) {
if (!singleton) {
singleton = [[Singleton alloc] init];
}
}
return singleton;
}
- 加鎖以后,當(dāng)多個線程同時調(diào)用shareInstance時,由于@synchronized已經(jīng)加鎖,只能有一個線程創(chuàng)建singleton實例。
- 只有在singleton未創(chuàng)建時,加鎖才是必要的。如果singleton已經(jīng)創(chuàng)建,這個時候還加鎖的話,會影響性能
三、多線程加鎖單例
+ (instancetype)sharedLoadData {
static Singleton *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[Singleton alloc] init];
});
return singleton;
}
- dispatch_once 無論使用多線程還是單線程,都只執(zhí)行一次
- GCD創(chuàng)建單例不僅可以解決多條線程的線程安全問題,也能保證性能,是官方推薦的方式。
- dispatch_once主要是根據(jù)onceToken的值來決定怎么去執(zhí)行代碼。
- 當(dāng)onceToken=0時,線程執(zhí)行dispatch_once的block中代碼
- 當(dāng)onceToken=-1時,線程跳過dispatch_once的block中代碼不執(zhí)行
- 當(dāng)onceToken為其他值時,線程被阻塞,等待onceToken值改變
dispatch_once執(zhí)行的流程
- 當(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。
單例的健壯性
要是自己用的話,直接用shareInstance方法創(chuàng)建沒啥問題,但是如果同組或者別人沒注意用alloc創(chuàng)建、或者別人不小心使用copy、mutableCopy就可能產(chǎn)生兩個實例,也就不存在單例。健壯性就是要保持怎么創(chuàng)建就這個實力,就返回位子的內(nèi)存地址。
static Singleton* _instance = nil;
+ (instancetype)shareInstance {
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
_instance = [[super allocWithZone:NULL] init] ;
//不是使用alloc方法,而是調(diào)用[[super allocWithZone:NULL] init]
//已經(jīng)重載allocWithZone基本的對象分配方法,所以要借用父類(NSObject)的功能來幫助出處理底層內(nèi)存分配的雜物
}) ;
return _instance ;
}
//用alloc返回也是唯一實例
+(id) allocWithZone:(struct _NSZone *)zone {
return [Singleton shareInstance] ;
}
//對對象使用copy也是返回唯一實例
-(id)copyWithZone:(NSZone *)zone {
return [Singleton shareInstance] ;//return _instance;
}
//對對象使用mutablecopy也是返回唯一實例
-(id)mutableCopyWithZone:(NSZone *)zone {
return [Singleton shareInstance] ;
}
@end
上面代碼注意點(diǎn):
- 當(dāng)static關(guān)鍵字修飾局部變量時,只會初始化一次且在程序中只有一份內(nèi)存
- copyWithZone mutablecopyWithZone 這個類遵守<NSCopying,NSMutableCopying>協(xié)議
- 如果_instance = [self alloc] init];創(chuàng)建的話,將會和-(id) allocWithZone:(struct _NSZone *)zone產(chǎn)生死鎖。 dispatch_once中的onceToken線程被阻塞,等待onceToken值改變。
- 當(dāng)用alloc創(chuàng)建對象、以及對對象進(jìn)行copy mutableCopy也是返回唯一實例
結(jié)尾
寫一個健壯的單例,知其所以然,才能更好的理解的單例。大家加油??!