2011-09-01 82 views
16

我讀的OBJ-C上的單身幾個驚人的資源:Objective-C單例如何實現init方法?

  1. SO問題:What does your Objective-C singleton look like?
  2. 週五Q &答:Care and Feeding of Singletons
  3. 蘋果文檔:Creating a Singleton Instance

但沒有這些資源中的init方法概念明確,雖然仍然是一個新手冰到Obj-C我很困惑,我應該如何實現它。

到目前爲止,我知道有init私人是不可能的對象 - 因爲它沒有提供真正的私有方法......所以這是可能的,用戶可以調用[[MyClass alloc] init]而不是使用我的[MyClass sharedInstance]的。

我的其他選擇是什麼?我相信我也應該處理我的單例的子類化場景。

回答

25

好吧,圍繞init的簡單方法就是不寫一個讓它調用默認的NSObject實現(它只返回self)。然後,對於你的sharedInstance函數,定義並調用一個私有函數,在你實例化你的單例時執行類似init的工作。 (這可以避免用戶不小心重新初始化你的單身人士。)

但是,主要問題是alloc被你的代碼的用戶調用!對於這一點,我個人推薦蘋果的首要allocWithZone:路線...

+ (id)allocWithZone:(NSZone *)zone 
{ 
    return [[self sharedInstance] retain]; 
} 

這意味着用戶仍然會得到您的單一實例,並且如果他們分配它,他們可以用錯誤和安全,因爲這一次釋放自定義alloc在單例上執行保留。 (注意:alloc來電allocWithZone:,不需要單獨覆蓋。)

希望有幫助!我們如果您想了解更多的信息我知道〜

編輯:擴大答案提供範例和更多的細節 -

以Catfish_Man的回答加以考慮,這是往往並不重要的是創造一個防彈單,並相反,只需在頭文件/文檔中寫一些合理的註釋,然後輸入assert即可。

但是,在我的情況下,我想要一個線程安全的延遲加載單例 - 也就是說,它不會被分配,直到它需要使用,而不是在應用程序啓動時自動分配。在學會了如何安全地做到這些之後,我想我可能會一路走下去。

編輯#2:我現在使用GCD的dispatch_once(...)爲線程安全的方法分配一個單一對象只有一次的應用程序的生命週期。見Apple Docs: GCD dispatch_once。我也還是添加allocWithZone:改寫位來自蘋果的老單實例,並添加了一個名爲singletonInit以防止不小心被多次調用私有的init:

//Hidden/Private initialization 
-(void)singletonInit 
{ 
    //your init code goes here 
} 

static HSCloudManager * sharedInstance = nil; 

+ (HSCloudManager *) sharedManager {         
    static dispatch_once_t dispatchOncePredicate = 0;     
    dispatch_once(&dispatchOncePredicate, ^{       
     sharedInstance = [[super allocWithZone:NULL] init];   
     [sharedInstance singletonInit];//Only place you should call singletonInit 
    });                 
    return sharedInstance;              
} 

+ (id) allocWithZone:(NSZone *)zone { 
    //If coder misunderstands this is a singleton, behave properly with 
    // ref count +1 on alloc anyway, and still return singleton! 
    return [[HSCloudManager sharedManager] retain]; 
} 

HSCloudManagerNSObject,並且不覆蓋init只剩下默認在NSObject執行,根據蘋果的文件只返回自我。這意味着[[HSCloudManager alloc] init][[[HSCloud Manager sharedManager] retain] self]相同,從而使作爲延遲加載單例的困惑用戶和多線程應用程序都安全。

至於你關心用戶的子類化你的單身人士,我會說只是評論/文件清楚。任何盲目分類而不讀書的人是要求疼痛!

EDIT#3:對於ARC兼容性,只是刪除從allocWithZone:重寫的保留部分,但保留覆蓋。

+0

@yAaak,似乎很公平,但我覺得在無限循環中,需要消化它;)我如何確保通過私有方法創建這種單例的過程是線程安全的?其他問題:如果我遵循Apple的'allocWithZone'思想,默認NSObject的'init'會被調用(只是返回'self'或者做其他事情?)...並且用戶嘗試用'alloc'和' init'是否改變了我的屬性/ ivars初始化和NSObject再次獲得'init'的任何事情? – matm

+0

yAak,你是對的:理智的態度你現在提出這樣的建議。我會使用你的代碼更多地使用單身人士:)我認爲你的答案相當廣泛,並考慮到Catfish_man的反對意見,所以我接受它。再次感謝! – matm

+0

我建議dispatch_once()而不是OSAtomic *。少挑剔,同樣快或更快。 –

2

init方法不應該受到影響。單身課程和普通課程一樣。您可能需要覆蓋allocWithZone:(由alloc調用)以避免創建您的課程的多個實例。

3

老實說?寫防彈單身課程的整個時尚似乎相當誇張地給我。如果您認真關注它,只需在第一次分配給它之前,將assert(sharedInstance == nil)放在那裏。這樣,如果有人誤用了它,它會崩潰,立即讓他們知道他們是白癡。

+2

部分我同意,但我的另一半說:爲什麼妥協?不過,我會盡我所能,並在標題/文檔中加上清晰的評論。 – matm

-1

試試這個

+(id)allocWithZone:(struct _NSZone *)zone{ 
    static dispatch_once_t onceToken_alloc; 
    static id __alloc_instance; 
    dispatch_once(&onceToken_alloc, ^{ 
     __alloc_instance = [super allocWithZone:zone]; 
    }); 
    return __alloc_instance; 
} 

-(id)init{ 
    static id __instance; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     __instance = [super init]; 
    }); 
    return __instance; 
} 
+0

這是分配一個新的實例,而不是返回單例實例。 – ebi

+0

不,它沒有。 – dotAramis

+0

試過了,你是對的,但是單身人士應該實現訪問類方法而不是要求消費者調用alloc來獲取實例。 – ebi

0

爲了使初始化/新方法,爲你的單例類的來電,你可以使用NS_UNAVAILABLE宏頭文件不可用:

- (id)init NS_UNAVAILABLE; 
+ (id)new NS_UNAVAILABLE; 

+ (instancetype)sharedInstance;