一、單例是什么?(apl??ke??(?)n 申請)
在 Foundation 和 Application Kit 框架中的一些類只允許創(chuàng)建單個對象,
即這些類在當(dāng)前進(jìn)程中的只有唯一一個實例。
舉例來說,NSFileManager (fa?l m?n?d??)和 NSWorkspace 類 在使用時都是基于進(jìn)程進(jìn)行單個對象的實例化。
當(dāng)向這些類請求實例的時候,它們會向您傳遞單一實例的一個引用,
如果該實例還不存在,則首先進(jìn)行對實例的分配內(nèi)存和初始化。
單件對象充當(dāng)控制中心的角色,負(fù)責(zé)指引或協(xié)調(diào)類的各種服務(wù)。
如果類在概念上只有一個實例(如:NSFileManager),就應(yīng)該產(chǎn)生一個單件實例,而不是多個實例;
二、為什么使用單例設(shè)計?簡單描述下對單利模式設(shè)計的理解?
1>單例設(shè)計是用來 限制一個類只能創(chuàng)建一個對象,
那么此對象中的屬性可以存儲全局共享的數(shù)據(jù),
所有的類都可以訪問 —->設(shè)置此單例對象中的屬性數(shù)據(jù)
2>如果一個類創(chuàng)建的時候非常耗費(fèi)性能,那么此類可以設(shè)置為單例節(jié)約性能,從而達(dá)到節(jié)省內(nèi)存資源,一個類就一個對象。
三、詳細(xì)介紹單例:
在iOS開發(fā)中,有很多地方都選擇使用單例模式。Singleton(s??ɡ(?)lt(?)n 單例模式)也叫單子模式,是一種常用的軟件設(shè)計模式。
有很多時候必須要創(chuàng)建一個對象,并且不能創(chuàng)建多個,用單例就為了防止創(chuàng)建多個對象。
單例模式的意思就是某一個類有且只有一個實例。在應(yīng)用這個模式時,單例對象的類必須保證只有一個實例存在。而且 自行實例化 并向整個系統(tǒng)提供這個實例。而這個類稱為單例類。
?。?!一個單例類可以實現(xiàn)在不同的窗口之間傳遞數(shù)據(jù)?。。?/p>
綜上所述單例模式的三要點(diǎn):
- 該類有且只有一個實例;
- 該類必須能夠自行創(chuàng)建這個實例;
- 該類必須能夠自行向整個系統(tǒng)提供這個實例。
單例模式的優(yōu)點(diǎn)與缺點(diǎn):
- 內(nèi)存占用與運(yùn)行時間
對比使用單例模式和非單例模式的例子,在內(nèi)存占用與運(yùn)行時間存在以下差距:
(1) 單例模式:單例模式每次獲取實例時都會先進(jìn)行判斷,看該實例是否存在:如果存在,則返回;否則,則創(chuàng)建實例。因此,會浪費(fèi)一些判斷的時間。但是,如果一直沒有人使用這個實例的話,那么就不會創(chuàng)建實例,節(jié)約了內(nèi)存空間。
(2) 非單例模式:當(dāng)類加載的時候就會創(chuàng)建類的實例,不管你是否使用它。然后當(dāng)每次調(diào)用的時候就不需要判斷該實例是否存在了,節(jié)省了運(yùn)行的時間。但是如果該實例沒有使用的話,就浪費(fèi)了內(nèi)存。
- 線程的安全性
(1) 從線程的安全性上來講,不加同步(@synchronized)單例模式是不安全的。比如,有兩個線程,一個是線程A,另外一個是線程B,如果它們同時調(diào)用某一個方法,那就可能會導(dǎo)致并發(fā)問題。在這種情況下,會創(chuàng)建出兩個實例來,也就是單例的控制在并發(fā)情況下失效了。
(2) 非單例模式的線程是安全的,因為程序保證只加載一次,在加載的時候不會發(fā)生并發(fā)情況。
(3) 單例模式如果要實現(xiàn)線程安全,只需要加上@synchronized即可。但是這樣一來,就會減低整個程序的訪問速度,而且每次都要判斷,比較麻煩。
(4) 雙重檢查加鎖:為了解決(3)的繁瑣問題,可以使用“雙重檢查加鎖”的方式來實現(xiàn),這樣,就可以既實現(xiàn)線程安全,又能使得程序性能不受太大的影響。
(4.1) 雙重檢查加鎖機(jī)制——并不是每次進(jìn)入要調(diào)用的方法都需要同步,而是先不同步,等進(jìn)入了方法之后,先檢查實例是否存在,如果不存在才進(jìn)入下面的同步塊,這是第一重檢查。當(dāng)進(jìn)入同步塊后,再次檢查實例是否存在,如果不存在,就在同步的情況下創(chuàng)建一個實例,這是第二重檢查。這樣一來,就只需要同步一次,從而減少了多次在同步情況下進(jìn)行判斷所浪費(fèi)的時間。
(4.2) 雙重檢查加鎖機(jī)制的實現(xiàn),會使用一個關(guān)鍵字volatile(v?l?t??l)。它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內(nèi)存的,從而確保了多個線程能正確的處理該變量。這種實現(xiàn)方式既可以實現(xiàn)線程安全地創(chuàng)建實例,而又不會對性能造成太大的影響。它只是在第一次創(chuàng)建實例的時候同步,以后就不需要同步了,從而加快了運(yùn)行速度。
3.實例控制:單例模式(Singleton) 會阻止其他對象實例化其自己的 Singleton 對象的副本,從而確保所有對象都訪問唯一實例。
4.靈活性:因為單例模式的類控制了實例化的過程,所以類可以更加靈活修改實例化過程。
四、iOS中的單例模式 如何實現(xiàn)一個單例類?
1. 在objective-c中要實現(xiàn)一個單例類,至少需要做以下四個步驟:
(1) 為單例對象創(chuàng)建一個靜態(tài)實例,可以寫成全局的,也可以在類方法里面實現(xiàn),并初始化,然后設(shè)置成nil;
(2) 實現(xiàn)一個實例構(gòu)造方法,檢查上面聲明的靜態(tài)實例是否為nil,如果是,則創(chuàng)建并返回一個本類的實例;
(3)重寫allocWithZone方法,用來保證其他人直接使用alloc和init試圖獲得一個新實例的時候不產(chǎn)生一個新實例,
(4)適當(dāng)實現(xiàn)allocWitheZone,copyWithZone,release和autorelease。2.怎樣實現(xiàn)一個單例模式的類,給出思路,不寫代碼?
1·首先必須創(chuàng)建一個全局實例,通常存放在一個全局變量中,此全局變量設(shè)置為nil
2·提供工廠方法對該全局實例進(jìn)行訪問,檢查該變量是否為nil,如果nil就創(chuàng)建一個新的實例,最后返回全局實例
3·全局變量的初始化在第一次調(diào)用工廠方法時會在+allocWithZone:中進(jìn)行,所以需要重寫該方法,防止通過標(biāo)準(zhǔn)的alloc方式創(chuàng)建新的實例
4·為了防止通過copy方法得到新的實例,需要實現(xiàn)-copyWithZone方法
5·只需在此方法中返回本身對象即可,引用計數(shù)也不需要進(jìn)行改變,因為單例模式下的對象是不允許銷毀的,所以也就不用保留
6·因為全局實例不允許釋放,所以在MRC 的項目里retain,release,autorelease方法均需重寫
五、單例設(shè)計模式的代碼具體實現(xiàn):
(1)寫一個簡單單例
//static關(guān)鍵字的作用有兩個,顯然在此的作用是下面作用的第一個。
//1. static作用于變量時,該變量只會定義一次,以后在使用時不會重新定義,
當(dāng)static作用于全局變量時說明: 該變量只能在當(dāng)前文件可以訪問,其他文件中不能訪問;
//2. static作用于函數(shù)時與作用于全局變量類時,
表示聲明或定義該函數(shù)是內(nèi)部函數(shù)(又叫靜態(tài)函數(shù)),
在該函數(shù)所在文件外的其他文件中無法訪問此函數(shù);
#import " File.h ";
static File * instance = nil;
@implementation File
//實現(xiàn)一個實例構(gòu)造方法檢查上面聲明的靜態(tài)實例是否為nil,
//如果 '是' 則 '' 新建'' 并 '' 返回 '' 一個本類的實例
+(id)shareInstance {
@synchronized(self){
if(instance == nil) {
instance = [[File alloc]init];
}
}
return instance;
}
```
#####(2) 寫一個單例 ( 里面有一個屬性)
.h 文件
@interface DataModel : NSObject
@property (strong, nonatomic) NSString* imageUrl;
+(DataModel*)sharedModel;
@end
.m文件
#import "DataModel.h"
@implementation DataModel
//為單例對象實現(xiàn)一個靜態(tài)實例,并初始化,然后設(shè)置成nil,
static DataModel* dataModel = nil;
+(DataModel*)sharedModel
{
if (dataModel == nil) {
dataModel = [[DataModel alloc] init];
}
return dataModel;
}
-(id)init
{
if (self = [super init]) {
//往往放一些要初始化的變量
self.imageUrl = [[NSString alloc] init];
}
return self;
} @end
>之后都需要 重寫allocWithZone方法、用來保證其他人直接使用alloc和init試圖獲得一個新實例子的時候不產(chǎn)生一個新實例,目的是限制這個類只創(chuàng)建一個對象。并在需要的時候重寫copyWithZone、retain、authorelease 等方法.
#####(3)以下是不同的創(chuàng)建方式,其實代碼都是大同小異:
//靜態(tài)的該類的實例
static ClassA * classA = nil;
@implementation ClassA
+ (ClassA *)sharedManager
{
@synchronized(self) {
if (!classA) {
classA = [[super allocWithZone:NULL]init];
}
return classA;
}
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self sharedManager] retain];
}
######(3.1)
//第一步:靜態(tài)實例,并初始化。
static MySingleton *sharedObj = nil;
@implementation MySingleton
//第二步:實例構(gòu)造檢查靜態(tài)實例是否為nil
- (MySingleton*) sharedInstance {
@synchronized (self) {
if (sharedObj == nil) {
sharedObj = [[self alloc] init];
}
}
return sharedObj;
}
//第三步:重寫allocWithZone方法 - (id) allocWithZone:(NSZone *)zone {
@synchronized (self) {
if (sharedObj == nil) {
sharedObj = [super allocWithZone:zone];
return sharedObj;
}
}
return nil;
}
- (id)init {
@synchronized(self) {
[super init];
return self;
}
}
//第四步在需要的時候重寫copyWithZone
- (id) copyWithZone:(NSZone *)zone {
return self;
}
//下面的是在MRC中重寫的,ARC 不考慮 具體問什么要重寫請看下一章
- (id) retain
{
return self;
}
- (unsigned) retainCount
{
return UINT_MAX;
// return NSUIntgerMax;
}
- (oneway void) release
{
}
- (id) autorelease
{
return self;
}
-(void)dealloc {
}
@end
##### 六簡單介紹下GCD實現(xiàn)單例模式(具體請看下一章)
iOS的單例模式有兩種官方寫法,如下:
//不使用GCD作對比:
import "ServiceManager.h"
static ServiceManager *defaultManager;
@implementation ServiceManager
+(ServiceManager *)defaultManager {
if(!defaultManager)
defaultManager=[[self allocWithZone:NULL] init];
return defaultManager;
}
@end
//使用GCD:在iOS4之后的另外一種寫法:
import "ServiceManager.h"
@implementation ServiceManager
static ServiceManager * sharedManager =nil ;
+(ServiceManager *)sharedManager {
static dispatch_once_t predicate;
//static ServiceManager * sharedManager =nil ;
dispatch_once(&predicate, ^{
sharedManager = [[ServiceManager alloc] init];
});
return sharedManager;
}
@end
/*當(dāng)用戶使用alloc init方法創(chuàng)建實體類時,
也可以保證所創(chuàng)建的事例對象是同一個。*/
//用類方法創(chuàng)建類的實體,方便外界使用。
- (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
//static ServiceManager * sharedManager =nil ;
dispatch_once(&onceToken, ^{
sharedManager = [super allocWithZone:zone];
});
return sharedManager;
}
//重寫copyWithZone方法,可以保證用戶在使用copy關(guān)鍵字時,創(chuàng)建的類的實例是同一個。
- (id)copyWithZone:(NSZone *)zone
{
return sharedManager;
}
當(dāng)然在xxx.h文件中需要
+(ServiceManager *)sharedManager; 接口。
而 dispatch_once_t 這個函數(shù),它可以保證整個應(yīng)用程序生命周期中某段代碼只被執(zhí)行一次!
// (instance ?nst(?)ns, 例子 share ???, 共用 )
該寫法來自 objcolumnist,文中提到,該寫法具有以下幾個特性:
- 線程安全。 2. 滿足靜態(tài)分析器的要求 3. 兼容了ARC
關(guān)于dispatch_once,這個函數(shù),下面是官方文檔介紹:
> dispatch_once
Executes a block object once and only once for the lifetime of an application.
void dispatch_once(
dispatch_once_t *predicate,
dispatch_block_t block);
Parameters
predicate
A pointer to a dispatch_once_t structure that is used to test whether the block has completed or not.
block
The block object to execute once.
Discussion
This function is useful for initialization of global data (singletons) in an application. Always call this function before using or testing any variables that are initialized by the block.
If called simultaneously from multiple threads, this function waits synchronously until the block has completed.
The predicate must point to a variable stored in global or static scope. The result of using a predicate with automatic or dynamic storage is undefined.
Availability
Available in iOS 4.0 and later.
Declared In
dispatch/once.h
> 我們看到,該方法的作用就是執(zhí)行且在整個程序的聲明周期中,僅執(zhí)行一次某一個block對象。簡直就是為單例而生的嘛。而且,有些我們需要在程序開頭初始化的動作,如果為了保證其,僅執(zhí)行一次,也可以放到這個dispatch_once來執(zhí)行。
然后我們看到它需要一個斷言來確定這個代碼塊是否執(zhí)行,這個斷言的指針要保存起來,相對于第一種方法而言,還需要多保存一個指針。
方法簡介中就說的很清楚了:對于在應(yīng)用中創(chuàng)建一個初始化一個全局的數(shù)據(jù)對象(單例模式),這個函數(shù)很有用。
如果同時在多線程中調(diào)用它,這個函數(shù)將等待同步等待,直至該block調(diào)用結(jié)束。
這個斷言的指針必須要全局化的保存,或者放在靜態(tài)區(qū)內(nèi)。使用存放在自動分配區(qū)域或者動態(tài)區(qū)域的斷言,dispatch_once執(zhí)行的結(jié)果是不可預(yù)知的。
總結(jié):1.這個方法可以在創(chuàng)建單例或者某些初始化動作時使用,以保證其唯一性。2.該方法是線程安全的,所以請放心大膽的在子線程中使用。(前提是你的dispatch_once_t *predicate對象必須是全局或者靜態(tài)對象。這一點(diǎn)很重要,如果不能保證這一點(diǎn),也就不能保證該方法只會被執(zhí)行一次。)