標籤:
在開發中經常會用到單例設計模式,目的就是為了在程式的整個生命週期內,只會建立一個類的執行個體對象,而且只要程式不被殺死,該執行個體對象就不會被釋放。下面我們來看看單例的概念、用途、如何建立,以便加深理解。
作用
在應用這個模式時,單例對象的類必須保證只有一個執行個體存在。許多時候整個系統只需要擁有一個的全域對象,這樣有利於我們協調系統整體的行為。比如在APP開發中我們可能在任何地方都要使用使用者的資訊,那麼可以在登入的時候就把使用者資訊存放在一個檔案裡面,這些配置資料由一個單例對象統一讀取,然後服務進程中的其他對象再通過這個單例對象擷取這些配置資訊。這種方式簡化了在複雜環境下的組態管理。
有的情況下,某個類可能只能有一個執行個體。比如說你寫了一個類用來播放音樂,那麼不管任何時候只能有一個該類的執行個體來播放聲音。再比如,一台電腦上可以連好幾個印表機,但是這個電腦上的列印程式只能有一個,這裡就可以通過單例模式來避免兩個列印任務同時輸出到印表機中,即在整個的列印過程中我只有一個列印程式的執行個體。
建立單例
有兩種方法來建立單例,下面分別介紹
1、GCD方式建立單例
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;}
上面兩種方式都可以建立單例,而且保證了使用者不管是通過shareInstance方法,還是alloc、copy方法得到的執行個體都是一樣的。
上面代碼的關鍵之處就在於如何在多線程情況下保證建立的單例還是同一個。
我們先看看在GCD情況下,如果不使用dispatch_once和同步鎖建立單例會出現什麼問題,去掉兩者後建立單例的代碼如下
+ (instancetype)sharedInstance { if (_instance == nil) { _instance = [[self alloc] init]; }}
假設此時有兩條線程:線程1和線程2,都在調用shareInstance方法來建立單例,那麼線程1運行到if (_instance == nil)出發現_instance = nil,那麼就會初始化一個_instance,假設此時線程2也運行到if的判斷處了,此時線程1還沒有建立完成執行個體_instance,所以此時_instance = nil還是成立的,那麼線程2又會建立一個_instace。
此時就建立了兩個執行個體對象,導致問題。
解決辦法1、使用dispatch_once
dispatch_once保證程式在運行過程中只會被運行一次,那麼假設此時線程1先執行shareInstance方法,建立了一個執行個體對象,線程2就不會再去執行dispatch_once的代碼了。從而保證了只會建立一個執行個體對象。
解決辦法2、使用互斥鎖
假設此時線程1在執行shareInstance方法,那麼synchronize大括弧內建立單例的代碼,如下所示:
if (_instance == nil) { _instance = [[self alloc] init]; }
就會被當做一個任務被加上了一把鎖。此時假設線程2也想執行shareInstance方法建立單例,但是看到了線程1加的互斥鎖,就會進入睡眠模式。等到線程1執行完畢,才會被喚醒,然後去執行上面所示的建立單例的代碼,但是此時_instance !=nil,所以不會再建立新的執行個體對象了。從而保證只會建立一個執行個體對象。
但是互斥鎖會影響效能,所以最好還是使用GCD方式建立單例。
宏建立單例
如果我們需要在程式中建立多個單例,那麼需要在每個類中都寫上一次上述代碼,非常繁瑣。
我們可以使用宏來封裝單例的建立,這樣任何類需要建立單例,只需要一行代碼就搞定了。
實現代碼
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; }如何調用
假設我們要在類viewcontroller中使用,調用方法如下:
viewcontroller.h檔案===========================#import #import "Singleton.h"@interface ViewController : UIViewControllerSingletonH(viewController)@endviewcontroller.m檔案===========================@interface ViewController ()@end@implementation ViewControllerSingletonM(ViewController)- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"%@ %@ %@ %@", [ViewController sharedViewController],[ViewController sharedViewController], [[ViewController alloc] init],[[ViewController alloc] init]);}@end輸出結果
可以看到四個對象的記憶體位址完全一樣,說明是同一個對象
iOS單例詳解