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