應(yīng)用場(chǎng)景
整個(gè)程序共用一份資源時(shí)(我們只需要對(duì)這份資源初始化一次)可以使用單例例如:
1.設(shè)置單例類訪問(wèn)應(yīng)用的配置信息
2.用戶的個(gè)人信息登陸后用nsuserdefaults 存儲(chǔ)劈伴,對(duì)登錄類進(jìn)一步采用單例封裝方便全局訪問(wèn)
3.封裝一個(gè)單例對(duì)應(yīng)用多處對(duì)同一本地?cái)?shù)據(jù)庫(kù)進(jìn)行操作
使用詳解
在開發(fā)中經(jīng)常會(huì)用到單例設(shè)計(jì)模式夹攒,目的就是為了在程序的整個(gè)生命周期內(nèi),只會(huì)創(chuàng)建一個(gè)類的實(shí)例對(duì)象,而且只要程序不被殺死迷帜,該實(shí)例對(duì)象就不會(huì)被釋放。下面我們來(lái)看看單例的概念袍暴、用途倒源、如何創(chuàng)建,以便加深理解孵稽。
作用
在應(yīng)用這個(gè)模式時(shí)许起,單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在十偶。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)的全局對(duì)象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為园细。比如在APP開發(fā)中我們可能在任何地方都要使用用戶的信息惦积,那么可以在登錄的時(shí)候就把用戶信息存放在一個(gè)文件里面,這些配置數(shù)據(jù)由一個(gè)單例對(duì)象統(tǒng)一讀取猛频,然后服務(wù)進(jìn)程中的其他對(duì)象再通過(guò)這個(gè)單例對(duì)象獲取這些配置信息狮崩。這種方式簡(jiǎn)化了在復(fù)雜環(huán)境下的配置管理。
有的情況下鹿寻,某個(gè)類可能只能有一個(gè)實(shí)例睦柴。比如說(shuō)你寫了一個(gè)類用來(lái)播放音樂(lè),那么不管任何時(shí)候只能有一個(gè)該類的實(shí)例來(lái)播放聲音毡熏。再比如坦敌,一臺(tái)計(jì)算機(jī)上可以連好幾個(gè)打印機(jī),但是這個(gè)計(jì)算機(jī)上的打印程序只能有一個(gè)痢法,這里就可以通過(guò)單例模式來(lái)避免兩個(gè)打印任務(wù)同時(shí)輸出到打印機(jī)中狱窘,即在整個(gè)的打印過(guò)程中我只有一個(gè)打印程序的實(shí)例。
創(chuàng)建單例
有兩種方法來(lái)創(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)建單例尖奔,而且保證了用戶不管是通過(guò)shareInstance方法搭儒,還是alloc、copy方法得到的實(shí)例都是一樣的越锈。
上面代碼的關(guān)鍵之處就在于如何在多線程情況下保證創(chuàng)建的單例還是同一個(gè)仗嗦。
我們先看看在GCD情況下,如果不使用dispatch_once和同步鎖創(chuàng)建單例會(huì)出現(xiàn)什么問(wèn)題甘凭,去掉兩者后創(chuàng)建單例的代碼如下
+ (instancetype)sharedInstance {
if (_instance == nil) {
_instance = [[self alloc] init];
}
}
假設(shè)此時(shí)有兩條線程:線程1和線程2稀拐,都在調(diào)用shareInstance方法來(lái)創(chuàng)建單例,那么線程1運(yùn)行到if (_instance == nil)出發(fā)現(xiàn)_instance = nil,那么就會(huì)初始化一個(gè)_instance丹弱,假設(shè)此時(shí)線程2也運(yùn)行到if的判斷處了德撬,此時(shí)線程1還沒(méi)有創(chuàng)建完成實(shí)例_instance,所以此時(shí)_instance = nil還是成立的躲胳,那么線程2又會(huì)創(chuàng)建一個(gè)_instace蜓洪。
此時(shí)就創(chuàng)建了兩個(gè)實(shí)例對(duì)象,導(dǎo)致問(wèn)題坯苹。
解決辦法1隆檀、使用dispatch_once
dispatch_once保證程序在運(yùn)行過(guò)程中只會(huì)被運(yùn)行一次,那么假設(shè)此時(shí)線程1先執(zhí)行shareInstance方法,創(chuàng)建了一個(gè)實(shí)例對(duì)象恐仑,線程2就不會(huì)再去執(zhí)行dispatch_once的代碼了泉坐。從而保證了只會(huì)創(chuàng)建一個(gè)實(shí)例對(duì)象。
解決辦法2裳仆、使用互斥鎖
假設(shè)此時(shí)線程1在執(zhí)行shareInstance方法腕让,那么synchronize大括號(hào)內(nèi)創(chuàng)建單例的代碼,如下所示:
if (_instance == nil) {
_instance = [[self alloc] init];
}
就會(huì)被當(dāng)做一個(gè)任務(wù)被加上了一把鎖歧斟。此時(shí)假設(shè)線程2也想執(zhí)行shareInstance方法創(chuàng)建單例纯丸,但是看到了線程1加的互斥鎖,就會(huì)進(jìn)入睡眠模式静袖。等到線程1執(zhí)行完畢觉鼻,才會(huì)被喚醒,然后去執(zhí)行上面所示的創(chuàng)建單例的代碼队橙,但是此時(shí)_instance !=nil,所以不會(huì)再創(chuàng)建新的實(shí)例對(duì)象了滑凉。從而保證只會(huì)創(chuàng)建一個(gè)實(shí)例對(duì)象。
但是互斥鎖會(huì)影響性能喘帚,所以最好還是使用GCD方式創(chuàng)建單例。
宏創(chuàng)建單例
如果我們需要在程序中創(chuàng)建多個(gè)單例咒钟,那么需要在每個(gè)類中都寫上一次上述代碼吹由,非常繁瑣。
我們可以使用宏來(lái)封裝單例的創(chuàng)建朱嘴,這樣任何類需要?jiǎng)?chuàng)建單例倾鲫,只需要一行代碼就搞定了。
實(shí)現(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
#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>
可以看到四個(gè)對(duì)象的內(nèi)存地址完全一樣乌昔,說(shuō)明是同一個(gè)對(duì)象。
參考
http://www.cocoachina.com/cms/wap.php?action=article&id=16661