iOS 單例

單例模式可能是設計模式中最簡單的形式了日矫,這一模式的意圖就是使得類中的一個對象成為系統(tǒng)中的唯一實例或辖。它提供了對類的對象所提供的資源的全局訪問點者填。因此需要用一種只允許生成對象類的唯一實例的機制刻伊。

下面讓我們來看下單例的作用:

  • 可以保證的程序運行過程训堆,一個類只有一個示例描验,而且該實例易于供外界訪問
  • 從而方便地控制了實例個數(shù),并節(jié)約系統(tǒng)資源坑鱼。

方法一(誤)

+ (instancetype)sharedInstance
{
    static Singleton *instance = nil;
    if (!instance) {
        instance = [[Singleton alloc] init];
    }
    return instance;
}

這種方式的單例不是線程安全的膘流。

假設此時有兩條線程:線程1和線程2,都在調(diào)用shareInstance方法來創(chuàng)建單例鲁沥,那么線程1運行到if (instance == nil)出發(fā)現(xiàn)instance = nil,那么就會初始化一個instance呼股,假設此時線程2也運行到if的判斷處了,此時線程1還沒有創(chuàng)建完成實例instance画恰,所以此時instance = nil還是成立的彭谁,那么線程2又會創(chuàng)建一個instace。

為了解決線程安全問題允扇,可以使用dispatch_once缠局、互斥鎖则奥。

方法二 (誤)

static Singleton *instance = nil;
+ (instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[Singleton alloc] init];
    });
    return instance;
}

static Singleton *instance = nil;
+ (instancetype)sharedInstance
{
    @synchronized (self) {
        if (!instance) {
            instance = [[Singleton alloc] init];
        }
    }
    return instance;
}

上面的兩個方法保證了線程安全,但是不夠全面甩鳄。如果使用其他方式創(chuàng)建逞度,能創(chuàng)建出不同的對象额划,違背了單例的設計原則妙啃。

Singleton *s = nil;
s = [Singleton sharedInstance];
NSLog(@"%@", s);
s = [[Singleton alloc] init];
NSLog(@"%@", s);
s = [Singleton new];
NSLog(@"%@", s);

打印出三個不同的地址

2016-12-21 20:46:30.414 Singleton[28843:2198096] <Singleton: 0x6000000168c0>
2016-12-21 20:46:30.415 Singleton[28843:2198096] <Singleton: 0x610000016340>
2016-12-21 20:46:30.415 Singleton[28843:2198096] <Singleton: 0x6180000164a0>

方法三(誤)

為了防止別人不小心利用alloc/init方式創(chuàng)建示例,也為了防止別人故意為之俊戳,我們要保證不管用什么方式創(chuàng)建都只能是同一個實例對象揖赴,這就得重寫另一個方法。

在方法二的基礎上增加重寫下面的方法:

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

再測試抑胎,發(fā)現(xiàn)打印出來的地址都一樣了燥滑。

但是,還沒結(jié)束阿逃。

我們添加一些屬性铭拧,并在-init方法中進行初始化:

@property (assign, nonatomic)int height;
@property (strong, nonatomic)NSObject *object;
@property (strong, nonatomic)NSMutableArray *array;

然后重寫-description方法:

- (NSString *)description
{
    NSString *result = @"";
    result = [result stringByAppendingFormat:@"<%@: %p>",[self class], self];
    result = [result stringByAppendingFormat:@" height = %d,",self.height];
    result = [result stringByAppendingFormat:@" array = %p,",self.array];
    result = [result stringByAppendingFormat:@" object = %p,",self.object];
    return result;
}

還用上面的方法,打印結(jié)果:

2016-12-21 20:58:03.523 Singleton[29239:2252268] <Singleton: 0x608000039d00> height = 10, arrayM = 0x60800005b150, object = 0x60800000b3e0,
2016-12-21 20:58:03.523 Singleton[29239:2252268] <Singleton: 0x608000039d00> height = 10, arrayM = 0x618000052540, object = 0x61800000b430,
2016-12-21 20:58:03.524 Singleton[29239:2252268] <Singleton: 0x608000039d00> height = 10, arrayM = 0x60800004ae00, object = 0x60800000b3e0,

可以看到恃锉,盡管使用的是同一個示例搀菩,可是他們的屬性卻不一樣。

因為盡管沒有為示例重新分配內(nèi)存空間破托,但是因為又執(zhí)行了init方法肪跋,會導致property被重新初始化。

方法四

為了保證屬性的初始化只執(zhí)行一次土砂,可以將屬性的初始化或者默認值設置也限制只執(zhí)行一次州既。我們這里加上dispatch_once。

+ (instancetype)sharedInstance
{
    return [[Singleton alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

- (instancetype)init
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super init];
        if (instance) {
            instance.height = 10;
            instance.object = [[NSObject alloc] init];
            instance.array = [[NSMutableArray alloc] init];
        }
    });
    return instance;
}

這種方式保證了單例的唯一萝映,也保證了屬性初始化的唯一吴叶。

關于線程安全

GCD的dispatch_once方式:保證程序在運行過程中只會被運行一次,那么假設此時線程1先執(zhí)行shareInstance方法序臂,創(chuàng)建了一個實例對象晤郑,線程2就不會再去執(zhí)行dispatch_once的代碼了。從而保證了只會創(chuàng)建一個實例對象贸宏。

互斥鎖方式:會把鎖內(nèi)的代碼當做一個任務造寝,這個任務執(zhí)行完畢前,不會被其他線程訪問吭练。

但是這種簡單的互斥鎖方式在每次調(diào)用單例時都會鎖一次诫龙,很影響性能,單例使用越頻繁鲫咽,影響越大签赃。

優(yōu)化互斥鎖方式

DCL(double check lock):雙重檢查模式是優(yōu)化了的互斥鎖方式谷异,過程就是check-lock-check,是對靜態(tài)變量instance的兩次判空锦聊。第一次判空避免了不必要的同步歹嘹,第二次判空是為了創(chuàng)建實例。

將上面的簡單互斥鎖方式修改一下:

 if (!instance) {
        @synchronized (self) {
            if (!instance) {
                instance = [super allocWithZone:zone];
            }
        }
    }
return instance;

DCL優(yōu)點是資源利用率高孔庭,第一次執(zhí)行時單例對象才被實例化尺上,效率高。缺點是第一次加載時反應稍慢一些圆到,在高并發(fā)環(huán)境下也有一定的缺陷怎抛,雖然發(fā)生的概率很小。

效率:
GCD > DCL > 簡單互斥鎖

使用+load或+initialize

load方法與initialize方法都會被Runtime自動調(diào)用一次芽淡,并且在Runtime情況下马绝,這兩個方法都是線程安全的。

根據(jù)這種特性挣菲,來實現(xiàn)單例類富稻。

+ (void)initialize
{
    if ([self class] == [Singleton class] && instance == nil) {
        instance = [[Singleton alloc] init];
        instance.height = 10;
        instance.object = [[NSObject alloc] init];
        instance.array = [[NSMutableArray alloc] init];
    }
}

+ (instancetype)sharedInstance
{
    return instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    if (instance == nil) {
        instance = [super allocWithZone:zone];
    }
    return instance;
}
  1. if([self class] == [Singleton class]...) 是為了保證 initialize方法只有在本類而非subclass時才執(zhí)行單例初始化方法。
  2. if (... && instance == nil) 是為了防止+initialize多次調(diào)用而產(chǎn)生多個實例(除了Runtime調(diào)用白胀,我們也可以顯示調(diào)用+initialize方法)椭赋。經(jīng)過測試,當我們將+initialize方法本身作為class的第一個方法執(zhí)行時纹笼,Runtime的+initialize會被先調(diào)用(這保證了線程安全)纹份,然后我們自己顯示調(diào)用的+initialize函數(shù)再被調(diào)用。 由于+initialize方法的第一次調(diào)用一定是Runtime調(diào)用廷痘,而Runtime又保證了線程安全性蔓涧,因此這里只簡單的檢測 singalObject == nil即可。

最好不用+load來做單例是因為它是在程序被裝載時調(diào)用的笋额,可能單例所依賴的環(huán)境尚未形成元暴,它比較適合對Class做設置。(更多關于+load和+initialize的知識兄猩,看這里)

使用宏

如果我們需要在程序中創(chuàng)建多個單例茉盏,那么需要在每個類中都寫上一次上述代碼,非常繁瑣枢冤。

我們可以使用宏來封裝單例的創(chuàng)建鸠姨,這樣任何類需要創(chuàng)建單例,只需要一行代碼就搞定了淹真。

#define SingletonH(name) + (instancetype)shared##name;

#define SingletonM(name)    \
static id instance = nil;   \
+ (instancetype)sharedInstance  \
{   \
    static dispatch_once_t onceToken;   \
    dispatch_once(&onceToken, ^{    \
        instance = [[[self class] alloc] init];  \
    }); \
    return instance;    \
}   \

其他

當然單例如果實現(xiàn)了NSCopying和NSMutableCopying協(xié)議讶迁,可以補充下面的方法:

- (id)copyWithZone:(NSZone *)zone
{
    return instance;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    return instance;
}

結(jié)束語

有關iOS設計模式的全部示例在這里examples

參考

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末核蘸,一起剝皮案震驚了整個濱河市巍糯,隨后出現(xiàn)的幾起案子啸驯,更是在濱河造成了極大的恐慌,老刑警劉巖祟峦,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罚斗,死亡現(xiàn)場離奇詭異,居然都是意外死亡宅楞,警方通過查閱死者的電腦和手機针姿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咱筛,“玉大人搓幌,你說我怎么就攤上這事杆故⊙嘎幔” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵处铛,是天一觀的道長饲趋。 經(jīng)常有香客問我,道長撤蟆,這世上最難降的妖魔是什么奕塑? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮家肯,結(jié)果婚禮上龄砰,老公的妹妹穿的比我還像新娘。我一直安慰自己讨衣,他們只是感情好换棚,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著反镇,像睡著了一般固蚤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上歹茶,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天夕玩,我揣著相機與錄音,去河邊找鬼惊豺。 笑死燎孟,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的尸昧。 我是一名探鬼主播揩页,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彻磁!你這毒婦竟也來了碍沐?” 一聲冷哼從身側(cè)響起狸捅,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎累提,沒想到半個月后尘喝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡斋陪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年朽褪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片无虚。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡缔赠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出友题,到底是詐尸還是另有隱情嗤堰,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布度宦,位于F島的核電站踢匣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏戈抄。R本人自食惡果不足惜离唬,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望划鸽。 院中可真熱鬧输莺,春花似錦、人聲如沸裸诽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崭捍。三九已至尸折,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間殷蛇,已是汗流浹背实夹。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粒梦,地道東北人亮航。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像匀们,于是被迫代替她去往敵國和親缴淋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 在開發(fā)中經(jīng)常會用到單例設計模式,目的就是為了在程序的整個生命周期內(nèi)重抖,只會創(chuàng)建一個類的實例對象露氮,而且只要程序不被殺死...
    零度_不結(jié)冰閱讀 441評論 0 0
  • 在iOS中有很多的設計模式,有一本書《Elements of Reusable Object-Oriented S...
    鄭明明閱讀 2,386評論 3 26
  • 單例一般作為:工具類 單例命名:一般情況下如果一個類是單例钟沛,那么就要提供一個類方法用于快速創(chuàng)建單例對象畔规,而且這個類...
    藍白自由閱讀 1,764評論 0 15
  • 在開發(fā)中經(jīng)常會用到單例設計模式,目的就是為了在程序的整個生命周期內(nèi)恨统,只會創(chuàng)建一個類的實例對象叁扫,而且只要程序不被殺死...
    Zsz丶少閱讀 415評論 0 0
  • 一、單例是什么畜埋?(apl??ke??(?)n 申請) 在 Foundation 和 Application Kit...
    藍白自由閱讀 6,052評論 6 22