前言
單例應(yīng)該是iOS中很簡(jiǎn)單的設(shè)計(jì)模式戈二,寫個(gè)單例很簡(jiǎn)單很方便。網(wǎng)上例子也很多喳资,大家也是基本上copy下來就可以了觉吭,但是要知其所以然這個(gè)問題的文章就很少。所以我在這寫一下好的單例仆邓,以及為什么這樣么寫鲜滩。
創(chuàng)建單例的幾種方式
一、單線程模式單例
+ (instancetype)sharedInsance{
static Singleton *singleton = nil;
if (!singleton ) {
singleton = [[Singleton alloc] init];
}
return singleton;
}
- 單線程單例只有在單個(gè)線程使用的情況下實(shí)用节值,在多線程的情況下徙硅,會(huì)產(chǎn)生線程不安全的情況。
- 我們還需要把a(bǔ)lloc方法變?yōu)樗接蟹椒ú判懈懔疲瑖?yán)格的單例是不允許再創(chuàng)建其他實(shí)例的嗓蘑,而alloc方法可以在外部任意生成實(shí)例
- 兩條線程里同時(shí)調(diào)用sharedLoadData方法,沒有鎖的話可能會(huì)產(chǎn)生兩個(gè)singleton實(shí)例匿乃,這樣單例就失去意義了桩皿。
二、多線程加鎖單例
+ (instancetype)sharedInsance {
static Singleton *singleton;
@synchronized (self) {
if (!singleton) {
singleton = [[Singleton alloc] init];
}
}
return singleton;
}
- 加鎖以后幢炸,當(dāng)多個(gè)線程同時(shí)調(diào)用shareInstance時(shí)泄隔,由于@synchronized已經(jīng)加鎖,只能有一個(gè)線程創(chuàng)建singleton實(shí)例宛徊。
- 只有在singleton未創(chuàng)建時(shí)佛嬉,加鎖才是必要的。如果singleton已經(jīng)創(chuàng)建岩调,這個(gè)時(shí)候還加鎖的話巷燥,會(huì)影響性能
三赡盘、多線程加鎖單例
+ (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時(shí)抛姑,線程執(zhí)行dispatch_once的block中代碼
- 當(dāng)onceToken=-1時(shí)赞厕,線程跳過dispatch_once的block中代碼不執(zhí)行
- 當(dāng)onceToken為其他值時(shí),線程被阻塞定硝,等待onceToken值改變
dispatch_once執(zhí)行的流程
- 當(dāng)線程調(diào)用shareInstance,此時(shí)onceToken = 0皿桑,調(diào)用block中的代碼。 此時(shí)onceToken的值變?yōu)?40734537148864。
- 當(dāng)其他線程再調(diào)用shareInstance方法時(shí)诲侮,onceToken的值已經(jīng)是140734537148864了镀虐,線程阻塞。
- 當(dāng)block線程執(zhí)行完block之后沟绪,onceToken變?yōu)?1.其他線程不再阻塞刮便,跳過block。
- 下次再調(diào)用shareInstance時(shí)绽慈,block已經(jīng)為-1.直接跳過block恨旱。
單例的健壯性
要是自己用的話,直接用shareInstance方法創(chuàng)建沒啥問題坝疼,但是如果同組或者別人沒注意用alloc創(chuàng)建搜贤、或者別人不小心使用copy、mutableCopy就可能產(chǎn)生兩個(gè)實(shí)例钝凶,也就不存在單例入客。健壯性就是要保持怎么創(chuàng)建就這個(gè)實(shí)力,就返回位子的內(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基本的對(duì)象分配方法,所以要借用父類(NSObject)的功能來幫助出處理底層內(nèi)存分配的雜物
}) ;
return _instance ;
}
//用alloc返回也是唯一實(shí)例
+(id) allocWithZone:(struct _NSZone *)zone {
return [Singleton shareInstance] ;
}
//對(duì)對(duì)象使用copy也是返回唯一實(shí)例
-(id)copyWithZone:(NSZone *)zone {
return [Singleton shareInstance] ;//return _instance;
}
//對(duì)對(duì)象使用mutablecopy也是返回唯一實(shí)例
-(id)mutableCopyWithZone:(NSZone *)zone {
return [Singleton shareInstance] ;
}
@end
上面代碼注意點(diǎn):
- 當(dāng)static關(guān)鍵字修飾局部變量時(shí)啃炸,只會(huì)初始化一次且在程序中只有一份內(nèi)存
- copyWithZone mutablecopyWithZone 這個(gè)類遵守<NSCopying,NSMutableCopying>協(xié)議
- 如果_instance = [self alloc] init];創(chuàng)建的話铆隘,將會(huì)和-(id) allocWithZone:(struct _NSZone *)zone產(chǎn)生死鎖。 dispatch_once中的onceToken線程被阻塞南用,等待onceToken值改變膀钠。
- 當(dāng)用alloc創(chuàng)建對(duì)象、以及對(duì)對(duì)象進(jìn)行copy mutableCopy也是返回唯一實(shí)例
結(jié)尾
寫一個(gè)健壯的單例裹虫,知其所以然肿嘲,才能更好的理解的單例。大家加油V雳窟!