在開(kāi)發(fā)中經(jīng)常會(huì)用到單例設(shè)計(jì)模式,目的就是為了在程序的整個(gè)生命周期內(nèi),只會(huì)創(chuàng)建一個(gè)類(lèi)的實(shí)例對(duì)象吼具,而且只要程序不被殺死被芳,該實(shí)例對(duì)象就不會(huì)被釋放。下面我們來(lái)看看單例的概念馍悟、用途、如何創(chuàng)建剩晴,以便加深理解锣咒。
作用
在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類(lèi)必須保證只有一個(gè)實(shí)例存在赞弥。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)的全局對(duì)象毅整,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。比如在APP開(kāi)發(fā)中我們可能在任何地方都要使用用戶(hù)的信息绽左,那么可以在登錄的時(shí)候就把用戶(hù)信息存放在一個(gè)文件里面悼嫉,這些配置數(shù)據(jù)由一個(gè)單例對(duì)象統(tǒng)一讀取,然后服務(wù)進(jìn)程中的其他對(duì)象再通過(guò)這個(gè)單例對(duì)象獲取這些配置信息拼窥。這種方式簡(jiǎn)化了在復(fù)雜環(huán)境下的配置管理戏蔑。
有的情況下,某個(gè)類(lèi)可能只能有一個(gè)實(shí)例鲁纠。比如說(shuō)你寫(xiě)了一個(gè)類(lèi)用來(lái)播放音樂(lè)总棵,那么不管任何時(shí)候只能有一個(gè)該類(lèi)的實(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)建單例,而且保證了用戶(hù)不管是通過(guò)shareInstance方法帜慢,還是alloc笼裳、copy方法得到的實(shí)例都是一樣的。
上面代碼的關(guān)鍵之處就在于如何在多線(xià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í)有兩條線(xiàn)程:線(xiàn)程1和線(xiàn)程2抽减,都在調(diào)用shareInstance方法來(lái)創(chuàng)建單例允青,那么線(xiàn)程1運(yùn)行到if (_instance == nil)出發(fā)現(xiàn)_instance = nil,那么就會(huì)初始化一個(gè)_instance,假設(shè)此時(shí)線(xiàn)程2也運(yùn)行到if的判斷處了卵沉,此時(shí)線(xiàn)程1還沒(méi)有創(chuàng)建完成實(shí)例_instance颠锉,所以此時(shí)_instance = nil還是成立的法牲,那么線(xiàn)程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í)線(xiàn)程1先執(zhí)行shareInstance方法悼瓮,創(chuàng)建了一個(gè)實(shí)例對(duì)象,線(xiàn)程2就不會(huì)再去執(zhí)行dispatch_once的代碼了艰猬。從而保證了只會(huì)創(chuàng)建一個(gè)實(shí)例對(duì)象横堡。
解決辦法2、使用互斥鎖
假設(shè)此時(shí)線(xiàn)程1在執(zhí)行shareInstance方法冠桃,那么synchronize大括號(hào)內(nèi)創(chuàng)建單例的代碼命贴,如下所示:
if (_instance == nil) {
_instance = [[self alloc] init];
}
就會(huì)被當(dāng)做一個(gè)任務(wù)被加上了一把鎖。此時(shí)假設(shè)線(xiàn)程2也想執(zhí)行shareInstance方法創(chuàng)建單例食听,但是看到了線(xiàn)程1加的互斥鎖胸蛛,就會(huì)進(jìn)入睡眠模式。等到線(xià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)中都寫(xiě)上一次上述代碼,非常繁瑣箩兽。
我們可以使用宏來(lái)封裝單例的創(chuàng)建津肛,這樣任何類(lèi)需要?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è)我們要在類(lèi)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é)果
QQ截圖20160612185918.png
可以看到四個(gè)對(duì)象的內(nèi)存地址完全一樣,說(shuō)明是同一個(gè)對(duì)象落包。