iOS 單例類詳解 ( 一 )

一、單例是什么?(apl??ke??(?)n 申請)

在 Foundation 和 Application Kit 框架中的一些類只允許創建單個對象,
即這些類在當前進程中的只有唯一一個實例。
舉例來說,NSFileManager (fa?l m?n?d??)和 NSWorkspace 類 在使用時都是基于進程進行單個對象的實例化。
當向這些類請求實例的時候,它們會向您傳遞單一實例的一個引用,
如果該實例還不存在,則首先進行對實例的分配內存和初始化。
單件對象充當控制中心的角色,負責指引或協調類的各種服務。
如果類在概念上只有一個實例(如:NSFileManager),就應該產生一個單件實例,而不是多個實例;

二、為什么使用單例設計?簡單描述下對單利模式設計的理解?

1>單例設計是用來 限制一個類只能創建一個對象,
那么此對象中的屬性可以存儲全局共享的數據,
所有的類都可以訪問 —->設置此單例對象中的屬性數據
2>如果一個類創建的時候非常耗費性能,那么此類可以設置為單例節約性能,從而達到節省內存資源,一個類就一個對象。

三、詳細介紹單例:

在iOS開發中,有很多地方都選擇使用單例模式。Singleton(s??ɡ(?)lt(?)n 單例模式)也叫單子模式,是一種常用的軟件設計模式。
有很多時候必須要創建一個對象,并且不能創建多個,用單例就為了防止創建多個對象。
單例模式的意思就是某一個類有且只有一個實例。在應用這個模式時,單例對象的類必須保證只有一個實例存在。而且 自行實例化 并向整個系統提供這個實例。而這個類稱為單例類。
!!!一個單例類可以實現在不同的窗口之間傳遞數據!!!

綜上所述單例模式的三要點:

  1. 該類有且只有一個實例;
  2. 該類必須能夠自行創建這個實例;
  3. 該類必須能夠自行向整個系統提供這個實例。

單例模式的優點與缺點:

  1. 內存占用與運行時間
    對比使用單例模式和非單例模式的例子,在內存占用與運行時間存在以下差距:
    (1) 單例模式:單例模式每次獲取實例時都會先進行判斷,看該實例是否存在:如果存在,則返回;否則,則創建實例。因此,會浪費一些判斷的時間。但是,如果一直沒有人使用這個實例的話,那么就不會創建實例,節約了內存空間。
    (2) 非單例模式:當類加載的時候就會創建類的實例,不管你是否使用它。然后當每次調用的時候就不需要判斷該實例是否存在了,節省了運行的時間。但是如果該實例沒有使用的話,就浪費了內存。
  1. 線程的安全性
    (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,文中提到,該寫法具有以下幾個特性:

  1. 線程安全。 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對象必須是全局或者靜態對象。這一點很重要,如果不能保證這一點,也就不能保證該方法只會被執行一次。)


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容

  • 單例模式(SingletonPattern)一般被認為是最簡單、最易理解的設計模式,也因為它的簡潔易懂,是項目中最...
    成熱了閱讀 4,284評論 4 34
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,810評論 18 139
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,197評論 30 471
  • 1 場景問題# 1.1 讀取配置文件的內容## 考慮這樣一個應用,讀取配置文件的內容。 很多應用項目,都有與應用相...
    七寸知架構閱讀 6,861評論 12 68
  • 一共收集了62個 Android Studio 使用小技巧和快捷鍵。 根據這些小技巧的使用場景,本文將這62個小技...
    01427271c047閱讀 485評論 0 3