سؤال

عادةً ما تكون طريقة الوصول الفردي الخاصة بي هي نوع من أنواع:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

ما الذي يمكنني فعله لتحسين هذا؟

هل كانت مفيدة؟

المحلول

خيار آخر هو استخدام +(void)initialize طريقة.من الوثائق:

وقت التشغيل يرسل initialize إلى كل فئة في البرنامج مرة واحدة بالضبط قبل إرسال الفصل، أو أي فئة ترث منه، رسالتها الأولى من داخل البرنامج.(وبالتالي قد لا يتم استدعاء الطريقة أبدًا إذا لم يتم استخدام الفئة.) يرسل وقت التشغيل ملف initialize رسالة إلى الطبقات بطريقة آمنة الموضوع.تتلقى الفئات الفائقة هذه الرسالة قبل الفئات الفرعية الخاصة بها.

لذلك يمكنك أن تفعل شيئًا مشابهًا لهذا:

static MySingleton *sharedSingleton;

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

نصائح أخرى

@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

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

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

    return sharedSingleton;
  }
}

@end

[مصدر]

في إجابتي الأخرى أدناه، أعتقد أنه يجب عليك القيام بما يلي:

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

منذ نشر كيندال مفردة آمنة للخيط تحاول تجنب تأمين التكاليف، اعتقدت أنني سأطرح واحدة أيضًا:

#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 غير موجود، فسيتم تخصيص مثيل ونسخه إليه باستخدام المقارنة والمبادلة ("CAS")؛

  3. الحالة المتنازع عليها:إذا حاول كلا الخيطين الاتصال sharedInstance في نفس الوقت و sharedInstance غير موجود في نفس الوقت، فسيقوم كلاهما بتهيئة مثيلات جديدة للمفردة ومحاولة CAS في موضعها.أيًا كان الفائز الذي يفوز بإرجاع CAS على الفور، وأي شخص يخسر يقوم بتحرير المثيل الذي خصصه للتو وإرجاع (تم تعيينه الآن) sharedInstance.الاوحد OSAtomicCompareAndSwapPtrBarrier يعمل كحاجز كتابة لخيط الإعداد وحاجز قراءة من خيط الاختبار.

static MyClass *sharedInst = nil;

+ (id)sharedInstance
{
    @synchronize( self ) {
        if ( sharedInst == nil ) {
            /* sharedInst set up in init */
            [[self alloc] init];
        }
    }
    return sharedInst;
}

- (id)init
{
    if ( sharedInst != nil ) {
        [NSException raise:NSInternalInconsistencyException
            format:@"[%@ %@] cannot be called; use +[%@ %@] instead"],
            NSStringFromClass([self class]), NSStringFromSelector(_cmd), 
            NSStringFromClass([self class]),
            NSStringFromSelector(@selector(sharedInstance)"];
    } else if ( self = [super init] ) {
        sharedInst = self;
        /* Whatever class specific here */
    }
    return sharedInst;
}

/* These probably do nothing in
   a GC app.  Keeps singleton
   as an actual singleton in a
   non CG app
*/
- (NSUInteger)retainCount
{
    return NSUIntegerMax;
}

- (oneway void)release
{
}

- (id)retain
{
    return sharedInst;
}

- (id)autorelease
{
    return sharedInst;
}

يحرر:هذا التنفيذ عفا عليه الزمن مع ARC.يرجى إلقاء نظرة على كيف يمكنني تنفيذ لغة Objective-C المفردة المتوافقة مع ARC؟ للتنفيذ الصحيح.

جميع تطبيقات التهيئة التي قرأتها في الإجابات الأخرى تشترك في خطأ شائع.

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

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

توصي وثائق Apple بالتحقق من نوع الفئة في كتلة التهيئة الخاصة بك.لأن الفئات الفرعية تستدعي التهيئة افتراضيًا.توجد حالة غير واضحة حيث يمكن إنشاء الفئات الفرعية بشكل غير مباشر من خلال KVO.لأنه إذا قمت بإضافة السطر التالي في فئة أخرى:

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

سيقوم Objective-C ضمنيًا بإنشاء فئة فرعية من MySingletonClass مما يؤدي إلى تشغيل ثانٍ لـ +initialize.

قد تعتقد أنه يجب عليك التحقق ضمنيًا من وجود تهيئة مكررة في كتلة init الخاصة بك على هذا النحو:

- (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;
}

TL;DR، هذا هو التنفيذ الخاص بي

@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.)

يوجد شرح شامل لكود Singleton الماكرو على مدونة Cocoa With Love

http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html.

لدي تنوع مثير للاهتمام في 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;
}

اجابة قصيرة:خلاب.

اجابة طويلة:شيء مثل....

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

تأكد من قراءة إرسال / رأس مرة واحدة لفهم ما يحدث.في هذه الحالة، تكون تعليقات الرأس أكثر قابلية للتطبيق من المستندات أو صفحة الدليل.

لقد قمت بإدخال المفردة في فصل دراسي، بحيث يمكن للفئات الأخرى أن ترث خصائص المفردة.

سينجلتون.ح :

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

القيد الوحيد حول فئة Singleton هو أنها فئة فرعية NSObject.لكن في معظم الأوقات أستخدم المفردات في الكود الخاص بي، فهي في الواقع فئات فرعية من NSObject، لذا فإن هذا الفصل يسهل حياتي ويجعل الكود أكثر نظافة.

يعمل هذا أيضًا في بيئة غير مجمعة للقمامة.

@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

ألا ينبغي أن يكون هذا آمنًا ويتجنب القفل الباهظ الثمن بعد المكالمة الأولى؟

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

هنا ماكرو التي قمت بتجميعها:

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

تعتمد على العمل هنا لمات غالاغرولكن تغيير التنفيذ للاستخدام طريقة swizzling كما هو موضح هنا بواسطة Dave MacLachlan من Google.

أرحب بالتعليقات / المساهمات.

ماذا عن

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

هل تتجنب تكلفة المزامنة بعد التهيئة؟

لإجراء مناقشة متعمقة حول النمط المفرد في Objective-C، انظر هنا:

استخدام نمط Singleton في Objective-C

كلسنجلتون هو:

  1. فئة فرعية (إلى الدرجة n)
  2. متوافق مع ARC
  3. آمن مع alloc و init
  4. محملة بتكاسل
  5. آمن للخيط
  6. خالي من القفل (يستخدم +initialize، وليس @synchronize)
  7. خالية من الماكرو
  8. خالي من التشويش
  9. بسيط

KLSingleton

لا تريد المزامنة مع نفسك...بما أن الكائن الذاتي غير موجود بعد!ينتهي بك الأمر إلى قفل قيمة معرف مؤقتة.تريد التأكد من عدم تمكن أي شخص آخر من تشغيل أساليب الفئة (sharedInstance، alloc، allocWithZone:، إلخ)، لذلك تحتاج إلى المزامنة على كائن الفئة بدلاً من ذلك:

@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

أردت فقط أن أترك هذا هنا حتى لا أفقده.الميزة في هذا هو أنه قابل للاستخدام في InterfaceBuilder، وهي ميزة كبيرة. وهذا مأخوذ من سؤال آخر طرحته:

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;
}
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

أعلم أن هناك الكثير من التعليقات على هذا "السؤال"، لكنني لا أرى الكثير من الأشخاص يقترحون استخدام ماكرو لتحديد المفردة.إنه نمط شائع ويعمل الماكرو على تبسيط المفردة إلى حد كبير.

فيما يلي وحدات الماكرو التي كتبتها بناءً على العديد من تطبيقات Objc التي رأيتها.

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

لماذا ماكرو الواجهة عندما يكون فارغًا تقريبًا؟تناسق التعليمات البرمجية بين ملفات الرأس والتعليمات البرمجية؛قابلية الصيانة في حالة رغبتك في إضافة المزيد من الطرق التلقائية أو تغييرها.

أنا أستخدم طريقة التهيئة لإنشاء المفردة كما هو مستخدم في الإجابة الأكثر شيوعًا هنا (في وقت كتابة هذا التقرير).

باستخدام أساليب فئة Objective C، يمكننا فقط تجنب استخدام النمط المفرد بالطريقة المعتادة، من:

[[Librarian sharedInstance] openLibrary]

ل:

[Librarian openLibrary]

عن طريق التفاف الفصل داخل فئة أخرى لديها للتو طرق الصف, ، وبهذه الطريقة لا توجد فرصة لإنشاء مثيلات مكررة عن طريق الخطأ، لأننا لا نقوم بإنشاء أي مثيل!

لقد كتبت مدونة أكثر تفصيلاً هنا :)

لتوسيع المثال من @ robbie-hanson ...

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;
}

طريقتي بسيطة كالآتي:

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.التحقق الثاني إذا (! تمت التهيئة) هو التأكد من عدم تهيئته بعد عندما يكتسب مؤشر الترابط الحالي القفل.

لم أقرأ جميع الحلول، لذا سامحوني إذا كان هذا الرمز زائدًا عن الحاجة.

هذا هو التنفيذ الأكثر أمانًا للخيط في رأيي.

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

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

    return sharedResourcesObj;
}

عادةً ما أستخدم رمزًا مشابهًا تقريبًا لذلك الموجود في إجابة بن هوفشتاين (والتي خرجت أيضًا من ويكيبيديا).أستخدمه للأسباب التي ذكرها كريس هانسون في تعليقه.

ومع ذلك، أحيانًا أحتاج إلى وضع قطعة مفردة في NIB، وفي هذه الحالة أستخدم ما يلي:

@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 (وما إلى ذلك) للقارئ، على الرغم من أن الكود أعلاه هو كل ما تحتاجه في بيئة تجميع البيانات المهملة.

الإجابة المقبولة، على الرغم من تجميعها، غير صحيحة.

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

لكل وثائق أبل:

...يمكنك اتباع أسلوب مماثل لمزامنة أساليب الفئة الخاصة بالفئة المرتبطة باستخدام كائن Class بدلاً من self.

حتى لو كان استخدام العمل ذاتيًا، فلا ينبغي ذلك، ويبدو لي أن هذا خطأ نسخ ولصق.سيكون التنفيذ الصحيح لطريقة مصنع الفصل هو:

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top