一、單例是什么?(apl??ke??(?)n 申請)
在 Foundation 和 Application Kit 框架中的一些類只允許創建單個對象,
即這些類在當前進程中的只有唯一一個實例。
舉例來說,NSFileManager (fa?l m?n?d??)和 NSWorkspace 類 在使用時都是基于進程進行單個對象的實例化。
當向這些類請求實例的時候,它們會向您傳遞單一實例的一個引用,
如果該實例還不存在,則首先進行對實例的分配內存和初始化。
單件對象充當控制中心的角色,負責指引或協調類的各種服務。
如果類在概念上只有一個實例(如:NSFileManager),就應該產生一個單件實例,而不是多個實例;
二、為什么使用單例設計?簡單描述下對單利模式設計的理解?
1>單例設計是用來 限制一個類只能創建一個對象,
那么此對象中的屬性可以存儲全局共享的數據,
所有的類都可以訪問 —->設置此單例對象中的屬性數據
2>如果一個類創建的時候非常耗費性能,那么此類可以設置為單例節約性能,從而達到節省內存資源,一個類就一個對象。
三、詳細介紹單例:
在iOS開發中,有很多地方都選擇使用單例模式。Singleton(s??ɡ(?)lt(?)n 單例模式)也叫單子模式,是一種常用的軟件設計模式。
有很多時候必須要創建一個對象,并且不能創建多個,用單例就為了防止創建多個對象。
單例模式的意思就是某一個類有且只有一個實例。在應用這個模式時,單例對象的類必須保證只有一個實例存在。而且 自行實例化 并向整個系統提供這個實例。而這個類稱為單例類。
!!!一個單例類可以實現在不同的窗口之間傳遞數據!!!
綜上所述單例模式的三要點:
- 該類有且只有一個實例;
- 該類必須能夠自行創建這個實例;
- 該類必須能夠自行向整個系統提供這個實例。
單例模式的優點與缺點:
- 內存占用與運行時間
對比使用單例模式和非單例模式的例子,在內存占用與運行時間存在以下差距:
(1) 單例模式:單例模式每次獲取實例時都會先進行判斷,看該實例是否存在:如果存在,則返回;否則,則創建實例。因此,會浪費一些判斷的時間。但是,如果一直沒有人使用這個實例的話,那么就不會創建實例,節約了內存空間。
(2) 非單例模式:當類加載的時候就會創建類的實例,不管你是否使用它。然后當每次調用的時候就不需要判斷該實例是否存在了,節省了運行的時間。但是如果該實例沒有使用的話,就浪費了內存。
- 線程的安全性
(1) 從線程的安全性上來講,不加同步(@synchronized)單例模式是不安全的。比如,有兩個線程,一個是線程A,另外一個是線程B,如果它們同時調用某一個方法,那就可能會導致并發問題。在這種情況下,會創建出兩個實例來,也就是單例的控制在并發情況下失效了。
(2) 非單例模式的線程是安全的,因為程序保證只加載一次,在加載的時候不會發生并發情況。
(3) 單例模式如果要實現線程安全,只需要加上@synchronized即可。但是這樣一來,就會減低整個程序的訪問速度,而且每次都要判斷,比較麻煩。
(4) 雙重檢查加鎖:為了解決(3)的繁瑣問題,可以使用“雙重檢查加鎖”的方式來實現,這樣,就可以既實現線程安全,又能使得程序性能不受太大的影響。
(4.1) 雙重檢查加鎖機制——并不是每次進入要調用的方法都需要同步,而是先不同步,等進入了方法之后,先檢查實例是否存在,如果不存在才進入下面的同步塊,這是第一重檢查。當進入同步塊后,再次檢查實例是否存在,如果不存在,就在同步的情況下創建一個實例,這是第二重檢查。這樣一來,就只需要同步一次,從而減少了多次在同步情況下進行判斷所浪費的時間。
(4.2) 雙重檢查加鎖機制的實現,會使用一個關鍵字volatile(v?l?t??l)。它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存的,從而確保了多個線程能正確的處理該變量。這種實現方式既可以實現線程安全地創建實例,而又不會對性能造成太大的影響。它只是在第一次創建實例的時候同步,以后就不需要同步了,從而加快了運行速度。
3.實例控制:單例模式(Singleton) 會阻止其他對象實例化其自己的 Singleton 對象的副本,從而確保所有對象都訪問唯一實例。
4.靈活性:因為單例模式的類控制了實例化的過程,所以類可以更加靈活修改實例化過程。
四、iOS中的單例模式 如何實現一個單例類?
1. 在objective-c中要實現一個單例類,至少需要做以下四個步驟:
(1) 為單例對象創建一個靜態實例,可以寫成全局的,也可以在類方法里面實現,并初始化,然后設置成nil;
(2) 實現一個實例構造方法,檢查上面聲明的靜態實例是否為nil,如果是,則創建并返回一個本類的實例;
(3)重寫allocWithZone方法,用來保證其他人直接使用alloc和init試圖獲得一個新實例的時候不產生一個新實例,
(4)適當實現allocWitheZone,copyWithZone,release和autorelease。2.怎樣實現一個單例模式的類,給出思路,不寫代碼?
1·首先必須創建一個全局實例,通常存放在一個全局變量中,此全局變量設置為nil
2·提供工廠方法對該全局實例進行訪問,檢查該變量是否為nil,如果nil就創建一個新的實例,最后返回全局實例
3·全局變量的初始化在第一次調用工廠方法時會在+allocWithZone:中進行,所以需要重寫該方法,防止通過標準的alloc方式創建新的實例
4·為了防止通過copy方法得到新的實例,需要實現-copyWithZone方法
5·只需在此方法中返回本身對象即可,引用計數也不需要進行改變,因為單例模式下的對象是不允許銷毀的,所以也就不用保留
6·因為全局實例不允許釋放,所以在MRC 的項目里retain,release,autorelease方法均需重寫
五、單例設計模式的代碼具體實現:
(1)寫一個簡單單例
//static關鍵字的作用有兩個,顯然在此的作用是下面作用的第一個。
//1. static作用于變量時,該變量只會定義一次,以后在使用時不會重新定義,
當static作用于全局變量時說明: 該變量只能在當前文件可以訪問,其他文件中不能訪問;
//2. static作用于函數時與作用于全局變量類時,
表示聲明或定義該函數是內部函數(又叫靜態函數),
在該函數所在文件外的其他文件中無法訪問此函數;
#import " File.h ";
static File * instance = nil;
@implementation File
//實現一個實例構造方法檢查上面聲明的靜態實例是否為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
//為單例對象實現一個靜態實例,并初始化,然后設置成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試圖獲得一個新實例子的時候不產生一個新實例,目的是限制這個類只創建一個對象。并在需要的時候重寫copyWithZone、retain、authorelease 等方法.
#####(3)以下是不同的創建方式,其實代碼都是大同小異:
//靜態的該類的實例
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)
//第一步:靜態實例,并初始化。
static MySingleton *sharedObj = nil;
@implementation MySingleton
//第二步:實例構造檢查靜態實例是否為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實現單例模式(具體請看下一章)
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
/*當用戶使用alloc init方法創建實體類時,
也可以保證所創建的事例對象是同一個。*/
//用類方法創建類的實體,方便外界使用。
- (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關鍵字時,創建的類的實例是同一個。
- (id)copyWithZone:(NSZone *)zone
{
return sharedManager;
}
當然在xxx.h文件中需要
+(ServiceManager *)sharedManager; 接口。
而 dispatch_once_t 這個函數,它可以保證整個應用程序生命周期中某段代碼只被執行一次!
// (instance ?nst(?)ns, 例子 share ???, 共用 )
該寫法來自 objcolumnist,文中提到,該寫法具有以下幾個特性:
- 線程安全。 2. 滿足靜態分析器的要求 3. 兼容了ARC
關于dispatch_once,這個函數,下面是官方文檔介紹:
> 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
> 我們看到,該方法的作用就是執行且在整個程序的聲明周期中,僅執行一次某一個block對象。簡直就是為單例而生的嘛。而且,有些我們需要在程序開頭初始化的動作,如果為了保證其,僅執行一次,也可以放到這個dispatch_once來執行。
然后我們看到它需要一個斷言來確定這個代碼塊是否執行,這個斷言的指針要保存起來,相對于第一種方法而言,還需要多保存一個指針。
方法簡介中就說的很清楚了:對于在應用中創建一個初始化一個全局的數據對象(單例模式),這個函數很有用。
如果同時在多線程中調用它,這個函數將等待同步等待,直至該block調用結束。
這個斷言的指針必須要全局化的保存,或者放在靜態區內。使用存放在自動分配區域或者動態區域的斷言,dispatch_once執行的結果是不可預知的。
總結:1.這個方法可以在創建單例或者某些初始化動作時使用,以保證其唯一性。2.該方法是線程安全的,所以請放心大膽的在子線程中使用。(前提是你的dispatch_once_t *predicate對象必須是全局或者靜態對象。這一點很重要,如果不能保證這一點,也就不能保證該方法只會被執行一次。)