1 單例模式
它是一種設(shè)計(jì)模式(常見(jiàn)的設(shè)計(jì)模式有:觀察者模式、工廠模式、門面模式等)。單例設(shè)計(jì)模式中,一個(gè)類只有一個(gè)實(shí)例,只分配一次內(nèi)存空間,節(jié)約內(nèi)存等,特別適合在移動(dòng)端使用。
實(shí)現(xiàn)單例的思路:
1 只能分配一次內(nèi)存----要攔截 alloc 方法
2 alloc 方法的底層是 allocWithZone 方法
3 每個(gè)類只有一個(gè)對(duì)象,需要有一個(gè)全局變量來(lái)存儲(chǔ)這個(gè)對(duì)象
4 需要考慮線程安全
1.1 單例基本形式(ARC)--懶漢模式
1.1.1 .h文件
@interface MusicTool : NSObject
//給外界快速生成單例對(duì)象使用
+(instancetype)sharedMusicTool;
@end
1.1.2 .m文件
@implementation MusicTool
//①定義全局靜態(tài)變量,用來(lái)存儲(chǔ)創(chuàng)建好的單例對(duì)象,當(dāng)外界需要時(shí),返回
static id _instance;
//②實(shí)現(xiàn)頭文件中的方法
+(instancetype)sharedMusicTool
{
//避免每次線程過(guò)來(lái)都加鎖,首先判斷一次,如果為空才會(huì)繼續(xù)加鎖并創(chuàng)建對(duì)象
if(_instance == nil)
{
//避免出現(xiàn)多個(gè)線程同時(shí)創(chuàng)建_instance,加鎖
@synchronized(self)
{
//使用懶加載,確保_instance 只創(chuàng)建一次
if(_instance == nil)
{
_instance = [[self alloc]init];
}
}
}
return _instance;
}
//③重寫 allocWithZone:方法---內(nèi)存與 sharedMusicTool方法體基本相同
+(instancetype)allocWithZone:(struct NSZone *)zone
{
//避免每次線程過(guò)來(lái)都加鎖,首先判斷一次,如果為空才會(huì)繼續(xù)加鎖并創(chuàng)建對(duì)象
if(_instance == nil)
{
//避免出現(xiàn)多個(gè)線程同時(shí)創(chuàng)建_instance,加鎖
@synchronized(self)
{
//使用懶加載,確保_instance 只創(chuàng)建一次
if(_instance == nil)
{
//調(diào)用父類方法,分配空間
_instance = [super allocWithZone:zone];
}
}
}
return _instance;
}
//④重寫 copyWithZone:方法,避免實(shí)例對(duì)象的 copy 操作導(dǎo)致創(chuàng)建新的對(duì)象
-(instancetype)copyWithZone:(NSZone *)zone
{
//由于是對(duì)象方法,說(shuō)明可能存在_instance對(duì)象,直接返回即可
return _instance;
}
@end
1.1.3 代碼解釋
(1)為什么全局變量要使用 static?
① static修飾局部變量
- 其生命周期與全局變量相同,直到程序結(jié)束,只有一份內(nèi)存空間
- 作用域不變
② static修飾全局變量 - 只有一份內(nèi)存空間
- 全局變量,在其他文件中,可以通過(guò) extern id _instance來(lái)聲明,然后直接在其他文件中調(diào)用。用 static 修飾后 在其他文件不能通過(guò) extern id _instance 聲明后 引用
(2)加鎖且懶加載的原理
懶加載是為了,確保整個(gè)類只有一個(gè)_instance,做到單例
加鎖:多線程中,可能多個(gè)線程都發(fā)現(xiàn)當(dāng)前的_instance==nil,那么就會(huì)同時(shí)創(chuàng)建對(duì)象,不符合單例的原則,所以加鎖。但是加鎖容易引起效率降低,不能每次線程過(guò)來(lái)就加鎖,所以在加鎖之前首先判斷一次是否為空,不為空根本不需要?jiǎng)?chuàng)建,直接返回。為空則說(shuō)明可能需要?jiǎng)?chuàng)建對(duì)象,那么再加鎖。
1.2 GCD簡(jiǎn)化單例(ARC)
在allocWithZone方法和 sharedSoundTool中,每次需要判斷是否為空,然后加鎖,其目的是為了保證 [[self alloc]init]和[super allocWithZone:zone]代碼只執(zhí)行一次,那么可以使用 GCD 的一次性代碼解決,另外,GCD 一次性代碼是線程安全的,所以不需要我們自己來(lái)處理加鎖問(wèn)題。
//修改 sharedSoundTool 方法
+(instancetype)sharedSoundTool
{
dispatch_once_t onceToken = NULL;
dispatch_once(&onceToken)
{
_instance = [[self alloc]init];
}
return _instance;
}
//修改 allocWithZone 方法
+(instancetype)allocWithZone:(struct NSZone *)zone
{
dispatch_once_t onceToken = NULL;
dispatch_once(&onceToken)
{
_instance = [super allocWithZone:zone];
}
return _instance;
}
1.3 GCD 簡(jiǎn)化單例(MRC)
在 MRC 環(huán)境中,我們需要考慮如果創(chuàng)建出來(lái)的單例對(duì)象,被手動(dòng) release 了怎么辦?所以我們?cè)谠O(shè)計(jì)單例模式的時(shí)候,需要考慮這種情況。如下:
retain,單例對(duì)象創(chuàng)建后,全局只有一個(gè)對(duì)象,所以一定要保證 retain 后仍然是自身,且引用計(jì)數(shù)不變
release,由于只有一個(gè)對(duì)象,被 release 后不能被釋放掉,所以 release 操作需要攔截
autorelease,與 release 一樣
-
retainCount,始終保證引用計(jì)數(shù)器為1
所以在 MRC 環(huán)境中,設(shè)計(jì)單例模式時(shí),還需要重寫下面四個(gè)方法//重寫 retain 方法,不作計(jì)數(shù)器加1的操作
-(instancetype)retain
{
return _instance;
}//重寫 release 方法,不做任何操作
-(void)release
{}
//重寫 autorelease 方法,返回自身
-(instancetype)autorelease
{
return _instance;
}//重寫 retainCount 方法,返回1
-(NSUInteger)retainCount
{
return 1;
}
1.4 餓漢模式的單例(不常用)
單例通常被分為兩種模式:懶漢模式和餓漢模式。
懶漢模式
當(dāng)使用這個(gè)單例對(duì)象的時(shí)候,才創(chuàng)建對(duì)象,就是_instance 的懶加載形式。由于移動(dòng)設(shè)備內(nèi)存有限,所以這種方式最適合。
餓漢模式
當(dāng)類第一次加載的時(shí)候,就創(chuàng)建單例對(duì)象,并保存在_instance 中。由于第一次加載就創(chuàng)建,內(nèi)存從程序開(kāi)始運(yùn)行的時(shí)候就分配了,不適合移動(dòng)設(shè)備。
load和initilized 方法
load方法
①當(dāng)程序剛開(kāi)始運(yùn)行的時(shí)候,所有的類都會(huì)加載到內(nèi)存中(不管這個(gè)類有沒(méi)有使用),此時(shí)就會(huì)調(diào)用 load 方法
②如果某種操作想要在程序運(yùn)行的過(guò)程中只執(zhí)行一次,那么這個(gè)操作就可以放到 load 方法中
③基于第二點(diǎn),我們的餓漢模式的單例對(duì)象創(chuàng)建就放在 load 方法中
initialized方法
①當(dāng)類第一次被使用的時(shí)候調(diào)用(比如,調(diào)用類的方法)。
②如果子類沒(méi)有重寫該方法,那么父類的initialized方法可能會(huì)被執(zhí)行多次。所以餓漢模式不能使用這個(gè)方法
.h文件
@interface SoundTool():NSObejct
//提供外界訪問(wèn)的方法
+(instancetype)sharedSoundTool;
@end
.m文件
@implementation SoundTool
//①定義靜態(tài)全局變量
static id _instance;
//②實(shí)現(xiàn)方法
+(instancetype)sharedSoundTool
{
return _instance;
}
//③重寫load方法
+(void)load
{
//不需要線程安全,類加載的時(shí)候線程還沒(méi)開(kāi)始呢
_instance = [[self alloc]init];
}
//④重寫allocWithZone方法
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
if(_instance == nil)
{
_instance = [super allocWithZone:zone];
}
return _instance;
}
@end
1.5 ARC和MRC的適配
上面的單例的設(shè)計(jì),分為ARC和MRC環(huán)境。MRC較ARC多了關(guān)于retain、release相關(guān)的代碼。為了能夠用同一份代碼適配不同的環(huán)境,我們可以是用條件編譯指令。
#if __has_feature(objc_arc)
//ARC編譯環(huán)境
#else
//MRC編譯環(huán)境
-(instancetype)retain{return _instance;}
-(void)release{}
-(instancetype)autorelease{return _instance;}
-(NSUIngeter)retainCount{return 1;}
#endif
1.6 宏實(shí)現(xiàn)單例
由于單例的h文件和m文件一成不變,所以可以抽成宏定義。抽成宏定義需要注意
1 宏定義后面如果要替換字符,需要用##拼接
#define SoundToolH(name) +(instancetype)shared##name;
//調(diào)用宏定義SoundToolH(MusicTool)時(shí),就相當(dāng)于
+(instancetype)sharedMusicTool;
2 宏定義后邊如果出現(xiàn)換行,需要用符號(hào)“ \ ” 來(lái)標(biāo)記下一行也是宏定義的部分,但最后一行末尾不需要
#define SoundToolM(name) \
static id _instance;\
+(instancetype)shared##name\
{\
dispatch_once_t onceToken = NULL;\
dispatch_once(&onceToken)\
{\
_instance = [self alloc]init];\
}\
}