在開發(fā)中經(jīng)常會用到單例設(shè)計模式熊响,目的就是為了在程序的整個生命周期內(nèi)昧碉,只會創(chuàng)建一個類的實例對象英染,而且只要程序不被殺死,該實例對象就不會被釋放被饿。下面我們來看看單例的概念四康、用途、如何創(chuàng)建狭握,以便加深理解闪金。
定義
保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
作用
- 在應(yīng)用這個模式時论颅,單例對象的類必須保證只有一個實例存在哎垦。許多時候整個系統(tǒng)只需要擁有一個的全局對象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為恃疯。比如在APP開發(fā)中我們可能在任何地方都要使用用戶的信息漏设,那么可以在登錄的時候就把用戶信息存放在一個文件里面,這些配置數(shù)據(jù)由一個單例對象統(tǒng)一讀取今妄,然后服務(wù)進(jìn)程中的其他對象再通過這個單例對象獲取這些配置信息郑口。這種方式簡化了在復(fù)雜環(huán)境下的配置管理。
- 有的情況下盾鳞,某個類可能只能有一個實例犬性。比如說你寫了一個類用來播放音樂,那么不管任何時候只能有一個該類的實例來播放聲音腾仅。再比如乒裆,一臺計算機(jī)上可以連好幾個打印機(jī),但是這個計算機(jī)上的打印程序只能有一個推励,這里就可以通過單例模式來避免兩個打印任務(wù)同時輸出到打印機(jī)中鹤耍,即在整個的打印過程中我只有一個打印程序的實例。
創(chuàng)建單例
有兩種方法來創(chuàng)建單例吹艇,下面分別介紹
1惰蜜、GCD方式創(chuàng)建單例
static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return _instance;
}
2、互斥鎖方式
static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
@synchronized(self) {
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
+ (instancetype)sharedInstance
{
@synchronized(self) {
if (_instance == nil) {
_instance = [[self alloc] init];
}
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
上面兩種方式都可以創(chuàng)建單例受神,而且保證了用戶不管是通過shareInstance方法抛猖,還是alloc、copy方法得到的實例都是一樣的。
上面代碼的關(guān)鍵之處就在于如何在多線程情況下保證創(chuàng)建的單例還是同一個财著。
我們先看看在GCD情況下联四,如果不使用dispatch_once和同步鎖創(chuàng)建單例會出現(xiàn)什么問題,去掉兩者后創(chuàng)建單例的代碼如下
+ (instancetype)sharedInstance
{
if (_instance == nil) {
_instance = [[self alloc] init];
}
}
假設(shè)此時有兩條線程:線程1和線程2撑教,都在調(diào)用shareInstance方法來創(chuàng)建單例朝墩,那么線程1運(yùn)行到if (_instance == nil)
出發(fā)現(xiàn)_instance = nil,那么就會初始化一個_instance,假設(shè)此時線程2也運(yùn)行到if的判斷處了伟姐,此時線程1還沒有創(chuàng)建完成實例_instance收苏,所以此時_instance = nil還是成立的,那么線程2又會創(chuàng)建一個_instace愤兵。
此時就創(chuàng)建了兩個實例對象鹿霸,導(dǎo)致問題。
解決辦法1秆乳、使用dispatch_once
dispatch_once保證程序在運(yùn)行過程中只會被運(yùn)行一次懦鼠,那么假設(shè)此時線程1先執(zhí)行shareInstance方法,創(chuàng)建了一個實例對象屹堰,線程2就不會再去執(zhí)行dispatch_once的代碼了肛冶。從而保證了只會創(chuàng)建一個實例對象。
解決辦法2扯键、使用互斥鎖
假設(shè)此時線程1在執(zhí)行shareInstance方法睦袖,那么synchronize大括號內(nèi)創(chuàng)建單例的代碼,如下所示:
if (_instance == nil) {
_instance = [[self alloc] init];
}
就會被當(dāng)做一個任務(wù)被加上了一把鎖忧陪。此時假設(shè)線程2也想執(zhí)行shareInstance方法創(chuàng)建單例扣泊,但是看到了線程1加的互斥鎖,就會進(jìn)入睡眠模式嘶摊。等到線程1執(zhí)行完畢,才會被喚醒评矩,然后去執(zhí)行上面所示的創(chuàng)建單例的代碼叶堆,但是此時_instance !=nil,所以不會再創(chuàng)建新的實例對象了。從而保證只會創(chuàng)建一個實例對象斥杜。
但是互斥鎖會影響性能虱颗,所以最好還是使用GCD方式創(chuàng)建單例。
宏創(chuàng)建單例
如果我們需要在程序中創(chuàng)建多個單例蔗喂,那么需要在每個類中都寫上一次上述代碼忘渔,非常繁瑣。
我們可以使用宏來封裝單例的創(chuàng)建缰儿,這樣任何類需要創(chuàng)建單例畦粮,只需要一行代碼就搞定了。
實現(xiàn)代碼
Singleton.h文件
==================================
#define SingletonH(name) + (instancetype)shared##name;
#define SingletonM(name) \
static id _instance; \
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
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; \
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone { \
return _instance; \
}
如何調(diào)用
假設(shè)我們要在類viewcontroller中使用,調(diào)用方法如下:
viewcontroller.h文件
===========================
#import <UIKit/UIKit.h>
#import "Singleton.h"
@interface ViewController : UIViewController
SingletonH(viewController)
@end
viewcontroller.m文件
===========================
@interface ViewController ()
@end
@implementation ViewController
SingletonM(ViewController)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@ %@ %@ %@", [ViewController sharedViewController],[ViewController sharedViewController], [[ViewController alloc] init],[[ViewController alloc] init]);
}
@end
輸出結(jié)果
<ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00>
可以看到四個對象的內(nèi)存地址完全一樣宣赔,說明是同一個對象