मेरा उद्देश्य-सी सिंगलटन कैसा दिखना चाहिए? [बन्द है]


334

मेरी सिंगलटन एक्सेसर विधि आमतौर पर कुछ प्रकार की होती है:

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    @synchronized(self)
    {
        if (gInstance == NULL)
            gInstance = [[self alloc] init];
    }

    return(gInstance);
}

इसे सुधारने के लिए मैं क्या कर सकता हूं?


27
आपके पास जो कुछ भी है वह ठीक है, हालाँकि आप वैश्विक चर घोषणा को अपनी + आवृत्ति विधि में स्थानांतरित कर सकते हैं (केवल उसी स्थान का उपयोग करने की आवश्यकता है, जब तक कि आप इसे सेट करने की अनुमति नहीं दे रहे हैं) और + defaultMyClass या + जैसे नाम का उपयोग करें आपके तरीके के लिए साझा किया गया। उदाहरण उदाहरण का खुलासा नहीं है।
क्रिस हैनसन

चूंकि यह इस सवाल का 'जवाब' देने की संभावना नहीं है, जल्द ही किसी भी समय बदल जाएगा, मैं इस सवाल पर एक ऐतिहासिक ताला लगा रहा हूं। दो कारण 1) बहुत सारे विचार, वोट, और अच्छी सामग्री 2) खुले / बंद के यो-यो को रोकने के लिए। यह अपने समय के लिए एक महान प्रश्न था, लेकिन स्टैक ओवरफ्लो के लिए इन प्रकारों के प्रश्न उपयुक्त नहीं हैं। अब हमारे पास वर्किंग कोड की जाँच के लिए कोड रिव्यू है । कृपया इस प्रश्न की सभी चर्चा इस मेटा प्रश्न पर करें
जॉर्ज स्टॉकर

जवाबों:


207

एक अन्य विकल्प +(void)initializeविधि का उपयोग करना है। प्रलेखन से:

रनटाइम initializeप्रत्येक प्रोग्राम में कक्षा से ठीक एक समय पहले, या उस से विरासत में प्राप्त किसी भी वर्ग को कार्यक्रम के भीतर से अपना पहला संदेश भेजता है। (इस प्रकार विधि का उपयोग कभी नहीं किया जा सकता है यदि वर्ग का उपयोग नहीं किया जाता है।) रनटाइम initializeसंदेश को थ्रेड-सुरक्षित तरीके से कक्षाओं में भेजता है। सुपरक्लास को यह संदेश उनके उपवर्गों से पहले प्राप्त होता है।

तो आप इसके लिए कुछ कर सकते हैं:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}

7
यदि रनटाइम केवल एक बार इसे कॉल करेगा, तो BOOL क्या करता है? क्या इस मामले में किसी ने एहतियात बरतते हुए इस समारोह को अपने कोड से स्पष्ट रूप से कहा है?
बाद में

5
हां, यह एक एहतियात है क्योंकि फ़ंक्शन को सीधे भी कहा जा सकता है।
रॉबी हैनसन

33
यह भी आवश्यक है क्योंकि उपवर्ग हो सकते हैं। यदि वे +initializeअपने सुपरक्लेसेस को ओवरराइड नहीं करते हैं, तो सबक्लास का पहली बार उपयोग किए जाने पर कार्यान्वयन कहा जाएगा।
स्वेन

3
@ पाओल आप releaseविधि को ओवरराइड कर सकते हैं और इसे खाली कर सकते हैं । :)

4
@aryaxt: सूचीबद्ध डॉक्स से, यह पहले से ही थ्रेड सुरक्षित है। तो, कॉल एक बार रनटाइम अवधि के अनुसार है। यह सही, थ्रेड-सेफ, आशावादी रूप से कुशल समाधान प्रतीत होगा।
लिलबर्डी

95
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;

  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];

    return sharedSingleton;
  }
}

@end

[स्रोत]


7
यह आप सभी को आमतौर पर एकल के लिए उपयोग करना चाहिए। अन्य बातों के अलावा, अपनी कक्षाओं को तुरंत अलग-अलग रखने से उन्हें परीक्षण करने में आसानी होती है, क्योंकि आप अपने राज्य को रीसेट करने के तरीके के बजाय अलग-अलग उदाहरणों का परीक्षण कर सकते हैं।
क्रिस हैनसन

3
Stig Brautaset: नहीं, इस उदाहरण में @synchronized को छोड़ना ठीक नहीं है। यह एक ही समय में इस स्थिर फ़ंक्शन को निष्पादित करने वाले दो थ्रेड्स की संभावित दौड़-स्थिति को संभालने के लिए है, दोनों एक ही समय में "अगर (अगर! शेयरिंगटन)" परीक्षण कर रहे हैं, और इस प्रकार दो [MySingleton आबंटित ”हुए हैं। .. Thesyn सिंक्रोनाइज्ड {स्कोप ब्लॉक} उस काल्पनिक थ्रेड को आगे बढ़ने से पहले अनुमति देने के लिए पहले थ्रेड के लिए प्रतीक्षा करने के लिए पहले थ्रेड के लिए प्रतीक्षा करता है। आशा है कि ये आपकी मदद करेगा! =)
मचाईथन

3
किसी को अभी भी वस्तु का अपना उदाहरण बनाने से क्या रोकता है? MySingleton *s = [[MySingelton alloc] init];
लिंडन लोमड़ी

1
@lindonfox आपके प्रश्न का उत्तर क्या है?
रफ़ी खाचदौरेन

1
@ रफी - क्षमा करें, मुझे लगता है कि मैं अपने उत्तर में चिपकाना भूल गया होगा। वैसे भी, मुझे किताब मिल गई है Pro Objective-C Design Patterns for iOSऔर यह पता चला है कि आप "सख्त" सिंगलटन कैसे बनाते हैं। मूल रूप से जब से आप आरंभ करने के तरीकों को निजी नहीं बना सकते हैं, तो आपको आवंटित विधियों को ओवरराइड और कॉपी करने की आवश्यकता है। इसलिए यदि आप कोशिश करते हैं और ऐसा कुछ करते हैं, [[MySingelton alloc] init]तो आपको एक रन टाइम एरर मिलेगा (हालांकि दुर्भाग्य से कंपाइल टाइम एरर नहीं)। मुझे समझ में नहीं आता है कि वस्तु निर्माण के सभी विवरण कैसे हैं, लेकिन आप इसे लागू करते हैं+ (id) allocWithZone:(NSZone *)zonesharedSingleton
lindon fox

59

नीचे मेरे अन्य उत्तर के अनुसार, मुझे लगता है कि आपको ऐसा करना चाहिए:

+ (id)sharedFoo
{
    static dispatch_once_t once;
    static MyFoo *sharedFoo;
    dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
    return sharedFoo;
}

6
ऊपर जो आप कर रहे हैं उससे सभी परेशान न हों। अपने (उम्मीद से बेहद कम) एकल को अलग-झटपट, और बस एक साझा / डिफ़ॉल्ट विधि है। आपने जो किया है वह केवल तभी आवश्यक है जब आप वास्तव में, वास्तव में, केवल अपनी कक्षा का एक ही उदाहरण चाहते हैं। जो आप नहीं करते, esp। इकाई परीक्षणों के लिए।
क्रिस हैनसन

बात यह है कि "एक सिंगलटन बनाने" के लिए Apple नमूना कोड है। लेकिन हाँ, आप बिल्कुल सही कह रहे हैं।
कॉलिन बैरेट

1
Apple नमूना कोड सही है यदि आप एक "सही" सिंगलटन चाहते हैं (यानी एक वस्तु जो केवल एक बार, कभी भी त्वरित हो सकती है), लेकिन जैसा कि क्रिस कहते हैं, यह शायद ही कभी आप चाहते हैं या ज़रूरत है जबकि किसी प्रकार का समझौता साझा उदाहरण है जो आप हैं आमतौर पर चाहते हैं।
ल्यूक रेडपथ

यहाँ उपरोक्त विधि के लिए एक मैक्रो है: gist.github.com/1057420 । यही है वह जो मेरे द्वारा उपयोग किया जाता है।
कोब्स्की

1
एक तरफ इकाई परीक्षण, इस समाधान के खिलाफ कुछ भी नहीं बोल रहा है, सही है? और यह तेज और सुरक्षित है।
LearnCocos2D

58

चूँकि केंडल ने एक थ्रेडसेफ़ सिंगलटन पोस्ट किया था जो लॉकिंग कॉस्ट से बचने की कोशिश करता था, मुझे लगा कि मैं एक टॉस करूँगा:

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

+ (className *) sharedInstance {                                                                    
  while (!sharedInstance) {                                                                          
    className *temp = [[self alloc] init];                                                                 
    if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
      [temp release];                                                                                   
    }                                                                                                    
  }                                                                                                        
  return sharedInstance;                                                                        
}

ठीक है, मुझे समझाएं कि यह कैसे काम करता है:

  1. फास्ट केस: सामान्य निष्पादन sharedInstanceमें पहले से ही सेट किया गया है, इसलिए whileलूप को कभी भी निष्पादित नहीं किया जाता है और फ़ंक्शन केवल चर के अस्तित्व के लिए परीक्षण करने के बाद लौटता है;

  2. धीमा मामला: यदि sharedInstanceअस्तित्व में नहीं है, तो एक उदाहरण आवंटित किया जाता है और इसकी तुलना और नकल करके इसमें एक कॉपी और स्वैप ('कैस') का उपयोग किया जाता है;

  3. तर्क मामला: दो धागे कॉल करने के लिए दोनों प्रयास तो sharedInstanceएक ही समय में और sharedInstance एक ही समय में मौजूद नहीं है तो वे सिंगलटन के दोनों इनिशियलाइज़ नई उदाहरणों और स्थिति में यह कैस का प्रयास करेंगे। जो भी कैस जीतता है वह तुरंत लौट जाता है, जो भी हारता है वह जारी किए गए इंस्टेंस को छोड़ देता है और (अब सेट) देता है sharedInstance। एकल OSAtomicCompareAndSwapPtrBarrierसेटिंग थ्रेड के लिए लेखन अवरोध और परीक्षण थ्रेड से रीड बैरियर दोनों के रूप में कार्य करता है।


18
यह एक आवेदन के जीवनकाल के दौरान हो सकता है कम से कम एक बार के लिए पूरा ओवरकिल है। फिर भी, यह स्पॉट-ऑन सही है, और तुलना-और-स्वैप तकनीक एक उपयोगी उपकरण है, जिसके बारे में जानने के लिए, +1।
स्टीव मैडसेन

अच्छा जवाब - ओएसएटॉमिक परिवार के बारे में जानने के लिए एक अच्छी बात है
बिल

1
@ लुईस: अद्भुत, वास्तव में ज्ञानवर्धक उत्तर! हालांकि एक सवाल: initआपके दृष्टिकोण में मेरा तरीका क्या होना चाहिए ? जब sharedInstanceइनिशियलाइज़ किया जाता है तो एक अपवाद को फेंकना एक अच्छा विचार नहीं है, मेरा मानना ​​है। उपयोगकर्ता को initसीधे कई बार कॉल करने से रोकने के लिए क्या करना है ?
मटमैला

2
मैं आमतौर पर इसे नहीं रोकता। आमतौर पर त्वरित रूप से त्वरित रूप से गुणा करने के लिए एक सिंगलटन की अनुमति देने के लिए अक्सर मान्य कारण होते हैं, कुछ विशेष प्रकार के यूनिट परीक्षण के लिए सबसे अधिक कॉमन्स होते हैं। अगर मैं वास्तव में एक भी उदाहरण लागू करना चाहता था, तो शायद यह देखने के लिए कि क्या वैश्विक अस्तित्व में है, यह देखने के लिए मेरे पास इनिट विधि की जाँच होगी, और अगर यह मैंने किया है तो यह स्वयं को छोड़ता है और वैश्विक वापस लौटता है।
लुईस गेरबारग

1
@ प्रतिक्रिया का थोड़ा देर से, लेकिन OSAtomicCompareAndSwapPtrBarrier को एक अस्थिर की आवश्यकता है। शायद वाष्पशील खोजशब्द संकलक को चेक के अनुकूलन से दूर रखने के लिए है? देखें: stackoverflow.com/a/5334727/449161 और developer.apple.com/library/mac/#documentation/Darwin/Reference/...
बेन फ्लिन

14
स्थिर MyClass * shareInst = nil;

+ (आईडी) साझा किया गया
{
    @synchronize (स्व) {
        अगर (साझा किया गया == नील) {
            init में स्थापित * * साझा
            [[आत्म आवंटन] init];
        }
    }
    साझा करें
}

- (आईडी) init
{
    अगर (साझा किया गया है! = nil) {
        [NSException raise: NSInternalInconsistencyException
            प्रारूप: @ "[% @% @] को नहीं बुलाया जा सकता; उपयोग + [% @% @] के बजाय"];
            NSStringFromClass ([स्वयं वर्ग]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([स्व वर्ग]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } और अगर (स्व = [सुपर init]) {
        साझा = स्व;
        / * यहां जो भी वर्ग विशिष्ट है * /
    }
    साझा करें
}

/ * ये शायद कुछ नहीं करते हैं
   एक जीसी ऐप। सिंगलटन रखता है
   में एक वास्तविक सिंगलटन के रूप में
   गैर सीजी अनुप्रयोग
* /
- (NSUInteger) रिटेनकाउंट
{
    NSUIntegerMax लौटें;
}

- (वनवे शून्य) रिलीज़
{
}

- (आईडी) बरकरार
{
    साझा करें
}

- (आईडी) ऑटोरेलिज
{
    साझा करें
}

3
मैंने देखा है कि अगर आपने [[self alloc] init]shareInst के परिणाम को असाइन नहीं किया है तो क्लैंग एक लीक के बारे में शिकायत करता है।
pix0r

इस तरह से सब-इनिट बदलना एक बहुत ही बदसूरत दृष्टिकोण IMO है। Init और / या वस्तु के वास्तविक निर्माण में गड़बड़ न करें। यदि आप इसके बजाय एक साझा उदाहरण तक पहुँच के एक नियंत्रित बिंदु के लिए जाते हैं, जबकि ऑब्जेक्ट में हार्ड-बेकिंग सिंगलटन नहीं है, तो आपके पास परीक्षण आदि लिखने के बाद एक खुशहाल समय होगा। हार्ड एकल बहुत दूर तक उपयोग नहीं किए जाते हैं।
13

12

संपादित करें: यह कार्यान्वयन ARC के साथ जुड़ा हुआ है। कृपया इस पर एक नज़र डालें कि मैं एक उद्देश्य-सी सिंगलटन कैसे लागू कर सकता हूं जो एआरसी के साथ संगत है? सही कार्यान्वयन के लिए।

आरंभिक सभी कार्यान्वयन मैंने अन्य उत्तरों में पढ़ा है, एक सामान्य त्रुटि साझा करते हैं।

+ (void) initialize {
  _instance = [[MySingletonClass alloc] init] // <----- Wrong!
}

+ (void) initialize {
  if (self == [MySingletonClass class]){ // <----- Correct!
      _instance = [[MySingletonClass alloc] init] 
  }
}

Apple प्रलेखन आपको अपने प्रारंभिक ब्लॉक में वर्ग प्रकार की जांच करने की सलाह देता है। क्योंकि उप-वर्ग डिफ़ॉल्ट रूप से इनिशियलाइज़ कॉल करते हैं। एक गैर-स्पष्ट मामला मौजूद है जहां उप-वर्ग केवीओ के माध्यम से अप्रत्यक्ष रूप से बनाए जा सकते हैं। यदि आप किसी अन्य कक्षा में निम्न पंक्ति जोड़ते हैं:

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]

ऑब्जेक्टिव-सी का अर्थ है कि माईसिंगटनक्लास का एक उपवर्ग बनाएगा, जिसके परिणामस्वरूप दूसरा ट्रिगर होगा +initialize

आप सोच सकते हैं कि आपको अपने इनिट ब्लॉक में डुप्लिकेट इनिशियलाइज़ेशन की जाँच करनी चाहिए जैसे:

- (id) init { <----- Wrong!
   if (_instance != nil) {
      // Some hack
   }
   else {
      // Do stuff
   }
  return self;
}

लेकिन आप अपने आप को पैर में गोली मार लेंगे; या इससे भी बदतर एक और डेवलपर को पैर में खुद को गोली मारने का अवसर देता है।

- (id) init { <----- Correct!
   NSAssert(_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self){
      // Do stuff
   }
   return self;
}

टीएल; डीआर, यहाँ मेरा कार्यान्वयन है

@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
   if (self == [MySingletonClass class]){
      _instance = [[MySingletonClass alloc] init];
   }
}

- (id) init {
   ZAssert (_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self) {
      // Initialization
   }
   return self;
}

+ (id) getInstance {
   return _instance;
}
@end

(हमारे अपने अभिकथन मैक्रो के साथ ZAssert बदलें; या सिर्फ NSAssert।)


1
मैं बस सरल रहूंगा और पूरी तरह से आरंभ से बचूंगा।
टॉम एंडरसन


9

मैं ShareInstance पर एक दिलचस्प भिन्नता है जो धागा सुरक्षित है, लेकिन आरंभीकरण के बाद लॉक नहीं होता है। अनुरोध के अनुसार शीर्ष उत्तर को संशोधित करने के लिए मुझे अभी तक पर्याप्त यकीन नहीं है, लेकिन मैं इसे आगे की चर्चा के लिए प्रस्तुत करता हूं:

// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;

// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
    return (MySingleton *)sharedInstance;
}

+ (MySingleton*)sharedInstance
{
    @synchronized(self)
    {
        if (sharedInstance == nil)
        {
            sharedInstance = [[MySingleton alloc] init];
            // Replace expensive thread-safe method 
            // with the simpler one that just returns the allocated instance.
            SEL origSel = @selector(sharedInstance);
            SEL newSel = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, origSel);
            Method newMethod = class_getClassMethod(self, newSel);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

1
+1 यह वाकई पेचीदा है। मैं एक क्लोन में class_replaceMethodबदलने के लिए उपयोग कर सकता हूं । इस तरह से आपको कभी भी लॉक को दोबारा प्राप्त करने के बारे में चिंता नहीं करनी होगी । sharedInstancesimpleSharedInstance@synchronized
डेव डोंगलांग

यह एक ही प्रभाव है, ExchangeImplementations का उपयोग करने का अर्थ है कि जब आप साझा किए गए इनस्टांस को कॉल करते हैं, तो आप वास्तव में simpleSedenInstance कह रहे हैं। मैंने वास्तव में रिप्लेसमेंट मेथोड से शुरू किया था, लेकिन यह तय किया कि इसे लागू करना बेहतर होगा ताकि मूल अभी भी जरूरत पड़ने पर मौजूद रहे ...
केंडल हेल्मस्टेटर गेलर

आगे के परीक्षण में, मैं रिप्लेसमैथोड को काम करने के लिए नहीं मिला - बार-बार कॉल में, कोड को अभी भी सरल साझाकरण कहा जाता है। मुझे लगता है कि यह हो सकता है क्योंकि वे दोनों क्लास लेवल मेथड हैं ... मेरे द्वारा इस्तेमाल किया गया रिप्लेसमेंट था: class_replaceMethod (self, OriginSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); और इसके कुछ रूपांतर। मैं अपने द्वारा पोस्ट किए गए कार्यों को सरलता से सत्यापित कर सकता हूं और साझाकरण के माध्यम से पहले पास के बाद simpleSedenInstance को कॉल किया जाता है।
केंडल हेल्मस्टेटर जेलर

आप एक थ्रेड सुरक्षित संस्करण बना सकते हैं जो रनटाइम मैकिंग का एक गुच्छा किए बिना इनिशियलाइज़ेशन के बाद लॉकिंग लागत का भुगतान नहीं करता है, मैंने नीचे एक कार्यान्वयन पोस्ट किया है।
लुई गेरबार

1
+1 महान विचार। मैं सिर्फ उन चीजों से प्यार करता हूं जो एक रनटाइम के साथ कर सकते हैं। लेकिन ज्यादातर मामलों में यह शायद समय से पहले का अनुकूलन है। अगर मुझे वास्तव में सिंक्रनाइज़ेशन लागत से छुटकारा पाना है तो मैं लुई द्वारा लॉकलेस संस्करण का उपयोग करूंगा।
स्वेन

6

संक्षिप्त उत्तर: शानदार।

लंबा जवाब: कुछ ऐसा…।

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

+ (id) instance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (instance == NULL){
            instance = [[super allocWithZone:NULL] init];
        }
    });
    return instance;
}

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

- (id) copyWithZone:(NSZone *)paramZone {
    return self;
}

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

प्रेषण / एक बार ज़रूर पढ़ें। यह समझने के लिए कि क्या चल रहा है। इस स्थिति में शीर्ष लेख टिप्पणियाँ डॉक्स या मैन पेज से अधिक लागू होती हैं।


5

मैंने एक वर्ग में सिंगलटन को रोल किया है, इसलिए अन्य वर्ग सिंगलटन गुणों को प्राप्त कर सकते हैं।

सिंगलटन:

static id sharedInstance = nil;

#define DEFINE_SHARED_INSTANCE + (id) sharedInstance {  return [self sharedInstance:&sharedInstance]; } \
                               + (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }

@interface Singleton : NSObject {

}

+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;

@end

सिंगलटन:

#import "Singleton.h"


@implementation Singleton


+ (id) sharedInstance { 
    return [self sharedInstance:&sharedInstance];
}

+ (id) sharedInstance:(id*)inst {
    @synchronized(self)
    {
        if (*inst == nil)
            *inst = [[self alloc] init];
    }
    return *inst;
}

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
    @synchronized(self) {
        if (*inst == nil) {
            *inst = [super allocWithZone:zone];
            return *inst;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

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

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}


@end

और यहाँ कुछ वर्ग का एक उदाहरण है, कि आप सिंगलटन बनना चाहते हैं।

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

सिंगलटन वर्ग के बारे में एकमात्र सीमा यह है कि यह NSObject उपवर्ग है। लेकिन ज्यादातर समय मैं अपने कोड में सिंगललेट्स का उपयोग करता हूं वे वास्तव में NSObject उपवर्ग हैं, इसलिए यह वर्ग वास्तव में मेरे जीवन को आसान बनाता है और कोड क्लीनर बनाता है।


आप कुछ अन्य लॉकिंग तंत्र का उपयोग करना चाह सकते हैं क्योंकि @synchronizedयह बहुत धीमा है और इसे टाला जाना चाहिए।
डार्कडस्ट

2

यह गैर-कचरा एकत्र पर्यावरण में भी काम करता है।

@interface MySingleton : NSObject {
}

+(MySingleton *)sharedManager;

@end


@implementation MySingleton

static MySingleton *sharedMySingleton = nil;

+(MySingleton*)sharedManager {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            [[self alloc] init]; // assignment not done here
        }
    }
    return sharedMySingleton;
}


+(id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            sharedMySingleton = [super allocWithZone:zone];
            return sharedMySingleton;  // assignment and return on first allocation
        }
    }
    return nil; //on subsequent allocation attempts return nil
}


-(void)dealloc {
    [super dealloc];
}

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


-(id)retain {
    return self;
}


-(unsigned)retainCount {
    return UINT_MAX;  //denotes an object that cannot be release
}


-(void)release {
    //do nothing    
}


-(id)autorelease {
    return self;    
}


-(id)init {
    self = [super init];
    sharedMySingleton = self;

    //initialize here

    return self;
}

@end

2

Shouln't यह थ्रेडसेफ़ है और पहली कॉल के बाद महंगी लॉकिंग से बचें?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}

2
यहां उपयोग की जाने वाली डबल- चेकिंग लॉकिंग तकनीक अक्सर कुछ वातावरणों में एक वास्तविक समस्या होती है (देखें aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf या Google इसे देखें)। अन्यथा दिखाए जाने तक, मुझे लगता है कि उद्देश्य-सी प्रतिरक्षा नहीं है। इसके अलावा wincent.com/a/knowledge-base/archives/2006/01/… देखें ।
स्टीव मैडसेन

2

यहाँ एक मैक्रो है जो मैंने एक साथ रखा है:

http://github.com/cjhanson/Objective-C-Optimized-Singleton

यह मैट गैलाघर द्वारा यहां काम पर आधारित है , लेकिन Google के डेव मैकलाचलन द्वारा वर्णित विधि स्विज़लिंग का उपयोग करने के कार्यान्वयन को बदलकर ।

मैं टिप्पणियों / योगदान का स्वागत करता हूं।


लिंक टूटा हुआ लगता है - मुझे वह स्रोत कहां मिल सकता है?
amok

2

कैसा रहेगा

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    if (gInstance == NULL) {
        @synchronized(self)
        {
            if (gInstance == NULL)
                gInstance = [[self alloc] init];
        }
    }

    return(gInstance);
}

तो आप प्रारंभ के बाद सिंक्रनाइज़ेशन लागत से बचें?


अन्य उत्तरों में दोहरी जाँच लॉकिंग की चर्चा देखें।
I_am_jorf


1

KLSingleton है:

  1. उपवर्गीय (एन-टी डिग्री के लिए)
  2. एआरसी संगत
  3. के साथ सुरक्षित allocऔरinit
  4. आलस्य से भरा हुआ
  5. सुरक्षित धागा
  6. लॉक-फ़्री (उपयोग करता है + प्रारंभ करें, नहीं @ सिंक्रनाइज़ करें)
  7. मैक्रो-मुक्त
  8. धूर्तता से मुक्त
  9. सरल

KLSingleton


1
मैं अपनी परियोजना के लिए आपके NSSingleton का उपयोग कर रहा हूं, और यह KVO के साथ असंगत लगता है। मामला यह है कि KVO NSKVONotifying_ MyClass को उपसर्ग करने के साथ प्रत्येक KVO ऑब्जेक्ट के लिए उप-वर्ग बनाता है । और यह MyClass + प्रारंभ और -init तरीकों को दो बार कहा जाता है।
ओलेग ट्रेखमैन

मैंने नवीनतम Xcode पर इसका परीक्षण किया और KVO घटनाओं को पंजीकृत करने या प्राप्त करने में कोई परेशानी नहीं हुई। आप इसे निम्न कोड के साथ सत्यापित कर सकते हैं: gist.github.com/3065038 जैसा कि मैंने ट्विटर पर उल्लेख किया है, + प्रारंभ विधियों को NSSingleton के लिए एक बार और प्रत्येक उपवर्ग के लिए एक बार बुलाया जाता है। यह ऑब्जेक्टिव-सी की संपत्ति है।
kevinlawler

यदि आप विधि में जोड़ते NSLog(@"initialize: %@", NSStringFromClass([self class]));हैं तो +initializeआप यह सत्यापित कर सकते हैं कि कक्षाएं केवल एक बार आरंभिक हैं।
केविनलावलर

NSLog (@ "प्रारंभ:% @", NSStringFromClass ([स्व वर्ग]));
ओलेग ट्रेखमैन

तुम भी यह आईबी संगत होना चाहते हो सकता है। मेरा है: stackoverflow.com/questions/4609609/…
Dan Rosenstark

0

आप स्व पर सिंक्रनाइज़ नहीं करना चाहते हैं ... क्योंकि स्व ऑब्जेक्ट अभी तक मौजूद नहीं है! आप अस्थायी आईडी मान पर लॉकिंग समाप्त करते हैं। आप यह सुनिश्चित करना चाहते हैं कि कोई अन्य वर्ग विधि (साझाकरण, आवंटन, आवंटन, आवंटन :, आदि) नहीं चला सकता है, इसलिए आपको इसके बजाय कक्षा ऑब्जेक्ट पर सिंक्रनाइज़ करने की आवश्यकता है:

@implementation MYSingleton

static MYSingleton * sharedInstance = nil;

+( id )sharedInstance {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ [ MYSingleton alloc ] init ];
    }

    return sharedInstance;
}

+( id )allocWithZone:( NSZone * )zone {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ super allocWithZone:zone ];
    }

    return sharedInstance;
}

-( id )init {
    @synchronized( [ MYSingleton class ] ) {
        self = [ super init ];
        if( self != nil ) {
            // Insert initialization code here
        }

        return self;
    }
}

@end

1
बाकी तरीके, एक्सेसर मेथड्स, म्यूटेटर मेथड्स आदि को सेल्फ पर सिंक करना चाहिए। सभी क्लास (+) मेथड्स और इनिशियलाइज़र (और शायद -dealloc) को क्लास ऑब्जेक्ट पर सिंक्रोनाइज़ करना चाहिए। यदि आप एक्सेसर / म्यूटेटर विधियों के बजाय ऑब्जेक्टिव-सी 2.0 गुणों का उपयोग करते हैं तो आप मैन्युअल रूप से सिंक करने से बच सकते हैं। सभी ऑब्जेक्ट .property और object.property = foo, स्वचालित रूप से स्वयं के लिए सिंक्रनाइज़ किए जाते हैं।
रोब डॉटसन

3
कृपया बताएं कि आपको क्यों लगता है कि selfऑब्जेक्ट एक क्लास विधि में मौजूद नहीं है। रनटाइम निर्धारित करता है कि किस पद्धति के कार्यान्वयन को उसी मान के आधार पर लागू करना है जो यह selfहर विधि (वर्ग या उदाहरण) को प्रदान करता है ।
ड्रीमलैक्स

2
एक वर्ग विधि के अंदर, self है वर्ग वस्तु। इसे स्वयं आज़माएँ:#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
jscs

0

बस मैं इसे यहाँ छोड़ना चाहता था इसलिए मैं इसे नहीं खोता। इसका लाभ यह है कि यह इंटरफ़ेसब्यूलेबल में उपयोग करने योग्य है, जो एक बड़ा लाभ है। यह मेरे द्वारा पूछे गए एक अन्य प्रश्न से लिया गया है :

static Server *instance;

+ (Server *)instance { return instance; }

+ (id)hiddenAlloc
{
    return [super alloc];
}

+ (id)alloc
{
    return [[self instance] retain];
}


+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        instance = [[Server hiddenAlloc] init];
    }
}

- (id) init
{
    if (instance)
        return self;
    self = [super init];
    if (self != nil) {
        // whatever
    }
    return self;
}

0
static mySingleton *obj=nil;

@implementation mySingleton

-(id) init {
    if(obj != nil){     
        [self release];
        return obj;
    } else if(self = [super init]) {
        obj = self;
    }   
    return obj;
}

+(mySingleton*) getSharedInstance {
    @synchronized(self){
        if(obj == nil) {
            obj = [[mySingleton alloc] init];
        }
    }
    return obj;
}

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    if(obj != self){
        [super release];
    }
    //do nothing
}

- (id)autorelease {
    return self;
}

-(void) dealloc {
    [super dealloc];
}
@end

0

मुझे पता है कि इस "प्रश्न" पर बहुत सारी टिप्पणियाँ हैं, लेकिन मैं बहुत से लोगों को एकल को परिभाषित करने के लिए मैक्रो का उपयोग करने का सुझाव नहीं देता। यह इतना सामान्य पैटर्न है और एक मैक्रो सिंगलटन को बहुत सरल करता है।

यहाँ मैक्रोज़ हैं जिन्हें मैंने कई ओब्जेक कार्यान्वयनों के आधार पर लिखा है जिन्हें मैंने देखा है।

Singeton.h

/**
 @abstract  Helps define the interface of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the implementation.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;


/**
 @abstract  Helps define the implementation of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the interface.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
    static BOOL initialized = NO; \
    if(!initialized) \
    { \
        initialized = YES; \
        __ ## NAME = [[TYPE alloc] init]; \
    } \
} \
\
\
+ (TYPE *)NAME \
{ \
    return __ ## NAME; \
}

उपयोग का उदाहरण:

MyManager.h

@interface MyManager

SingletonInterface(MyManager, sharedManager);

// ...

@end

MyManager.m

@implementation MyManager

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
    }

    return self;
}

SingletonImplementation(MyManager, sharedManager);

// ...

@end

लगभग खाली होने पर इंटरफ़ेस मैक्रो क्यों? हेडर और कोड फ़ाइलों के बीच कोड की स्थिरता; यदि आप अधिक स्वचालित विधियों को जोड़ना चाहते हैं या इसे बदलना चाहते हैं, तो बनाए रखने की स्थिति।

मैं सिंगलटन बनाने के लिए प्रारंभिक विधि का उपयोग कर रहा हूं जैसा कि यहां सबसे लोकप्रिय उत्तर (लेखन के समय) में उपयोग किया जाता है।


0

उद्देश्य सी वर्ग विधियों के साथ, हम सामान्य तरीके से सिंगलटन पैटर्न का उपयोग करने से बच सकते हैं:

[[Librarian sharedInstance] openLibrary]

सेवा:

[Librarian openLibrary]

कक्षा को किसी अन्य वर्ग के अंदर लपेटने से जिसमें सिर्फ क्लास मेथड्स होते हैं , उस तरह से गलती से डुप्लिकेट इंस्टेंसेस बनाने का कोई मौका नहीं है, क्योंकि हम कोई उदाहरण नहीं बना रहे हैं!

मैंने यहाँ अधिक विस्तृत ब्लॉग लिखा है :)


आपका लिंक अब कार्य नहीं करता है।
इ_म_जॉर्फ

0

@ रॉबी-हैंसन से उदाहरण का विस्तार करने के लिए ...

static MySingleton* sharedSingleton = nil;

+ (void)initialize {
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        sharedSingleton = [[self alloc] init];
    }
}

- (id)init {
    self = [super init];
    if (self) {
        // Member initialization here.
    }
    return self;
}

0

मेरा तरीका इस तरह सरल है:

static id instanceOfXXX = nil;

+ (id) sharedXXX
{
    static volatile BOOL initialized = NO;

    if (!initialized)
    {
        @synchronized([XXX class])
        {
            if (!initialized)
            {
                instanceOfXXX = [[XXX alloc] init];
                initialized = YES;
            }
        }
    }

    return instanceOfXXX;
}

यदि सिंगलटन पहले से ही आरंभिक है, तो LOCK ब्लॉक में प्रवेश नहीं किया जाएगा। दूसरा चेक अगर ((आरंभिक) यह सुनिश्चित करने के लिए है कि यह अभी तक आरंभीकृत नहीं किया गया है जब वर्तमान धागा लॉक को प्राप्त करता है।


यह स्पष्ट नहीं है कि अंकन initializedके रूप में volatileपर्याप्त है। देखें aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
I_am_jorf

0

मैंने सभी समाधानों के माध्यम से नहीं पढ़ा है, इसलिए यदि यह कोड निरर्थक है तो क्षमा करें।

मेरी राय में यह सबसे सुरक्षित धागा है।

+(SingletonObject *) sharedManager
{
    static SingletonObject * sharedResourcesObj = nil;

    @synchronized(self)
    {
        if (!sharedResourcesObj)
        {
            sharedResourcesObj = [[SingletonObject alloc] init];
        }
    }

    return sharedResourcesObj;
}

-4

मैं आमतौर पर बेन हॉफस्टीन के जवाब में कोड के समान उपयोग करता हूं (जो कि मैं विकिपीडिया से भी बाहर निकला था)। मैं क्रिस कॉनसन द्वारा उनकी टिप्पणी में बताए गए कारणों के लिए इसका उपयोग करता हूं।

हालांकि, कभी-कभी मुझे एक एनआईबी में एक सिंगलटन रखने की आवश्यकता होती है, और उस स्थिति में मैं निम्नलिखित का उपयोग करता हूं:

@implementation Singleton

static Singleton *singleton = nil;

- (id)init {
    static BOOL initialized = NO;
    if (!initialized) {
        self = [super init];
        singleton = self;
        initialized = YES;
    }
    return self;
}

+ (id)allocWithZone:(NSZone*)zone {
    @synchronized (self) {
        if (!singleton)
            singleton = [super allocWithZone:zone];     
    }
    return singleton;
}

+ (Singleton*)sharedSingleton {
    if (!singleton)
        [[Singleton alloc] init];
    return singleton;
}

@end

मैं -retainपाठक को (आदि) के कार्यान्वयन को छोड़ देता हूं , हालांकि उपरोक्त कोड आपको कचरा एकत्र किए गए वातावरण में चाहिए।


2
आपका कोड थ्रेड-सुरक्षित नहीं है। यह आवंटित विधि में सिंक्रनाइज़ का उपयोग करता है, लेकिन इनिट विधि में नहीं। प्रारंभिक बूल पर जाँच करना थ्रेड-सुरक्षित नहीं है।
मैकी

-5

स्वीकृत उत्तर, हालांकि यह संकलन करता है, गलत है।

+ (MySingleton*)sharedInstance
{
    @synchronized(self)  <-------- self does not exist at class scope
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

प्रति Apple प्रलेखन:

... आप स्वयं के बजाय क्लास ऑब्जेक्ट का उपयोग करके संबंधित वर्ग के वर्ग तरीकों को सिंक्रनाइज़ करने के लिए एक समान दृष्टिकोण ले सकते हैं।

यहां तक ​​कि अगर स्व कार्यों का उपयोग करते हैं, तो यह नहीं होना चाहिए और यह मुझे कॉपी और पेस्ट गलती की तरह दिखता है। एक वर्ग कारखाने विधि के लिए सही कार्यान्वयन होगा:

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

6
आत्म सबसे निश्चित रूप से करता है यह वर्ग गुंजाइश मौजूद हैं। यह कक्षा के उदाहरण के बजाय कक्षा को संदर्भित करता है। कक्षाएं (ज्यादातर) प्रथम श्रेणी की वस्तुएं हैं।
schwa

आप एक विधि के साथ @synchroninzed क्यों डालते हैं?
user4951

1
जैसा कि पहले ही कहा स्च्वा, self है एक वर्ग विधि के वर्ग ऑब्जेक्ट के अंदर। इसे प्रदर्शित करने वाले एक स्निपेट के लिए मेरी टिप्पणी देखें
jscs

selfमौजूद है, लेकिन पहचानकर्ता के रूप में इसका उपयोग करना @synchronizedउदाहरण के तरीकों तक पहुंच को सिंक्रनाइज़ करेगा। जैसा कि @ user490696 बताते हैं, ऐसे मामले (जैसे एकल) हैं, जहां कक्षा ऑब्जेक्ट का उपयोग करना बेहतर होता है। You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
ओब्ज
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.