Как должен выглядеть мой Синглтон Objective-C? [закрытый]


мой одноэлементный метод доступа обычно является некоторым вариантом:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

что я могу сделать, чтобы улучшить это?

26 334

26 ответов:

другой вариант-использовать +(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 возвращается немедленно, какой бы он ни проиграл, освобождает экземпляр, который он только что выделил, и возвращает (теперь установлено) 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;
}

Edit: эта реализация устарела с ARC. Пожалуйста, взгляните на как реализовать синглтон Objective-C, совместимый с ARC? для правильной реализации.

все реализации initialize, которые я читал в других ответах, имеют общую ошибку.

+ (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]

Objective-C неявно создаст подкласс MySingletonClass, что приведет к второму запуску +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;
}

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

подробное объяснение кода Одноэлементного макроса находится в блоге Cocoa With Love

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

У меня есть интересный вариант sharedInstance, который является потокобезопасным, но не блокируется после инициализации. Я еще не достаточно уверен в этом, чтобы изменить верхний ответ, как просили, но я представляю его для дальнейшего обсуждения:

// 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

обязательно прочитайте отправка/раз.H заголовка чтобы понять, что происходит. В этом случае комментарии заголовка более применимы, чем документы или man-страница.

Я свернул синглтон в класс, поэтому другие классы могут наследовать свойства синглтона.

Синглтон.h:

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, поэтому этот класс действительно облегчает мою жизнь и делает очиститель кода.

Это также работает в среде, не связанной с мусором.

@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, как описано здесь Дэйв Маклахлан из Google.

Я приветствую комментарии / взносов.

как о

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

таким образом, вы избегаете стоимости синхронизации после инициализации?

для углубленного обсуждения одноэлементного шаблона в Objective-C, посмотрите здесь:

использование Одноэлементного шаблона в Objective-C

KLSingleton is:

  1. подкласс (до n-й степени)
  2. ARC совместимый
  3. безопасная с alloc и init
  4. загружается лениво
  5. потокобезопасным
  6. Lock-free (использует + инициализировать, а не @synchronize)
  7. макрос-бесплатно
  8. Swizzle-free
  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

почему макрос интерфейса, когда он почти пуст? Согласованность кода между заголовком и файлами кода; ремонтопригодность в случае, если вы хотите добавить больше автоматических методов или изменить его.

Я использую метод initialize для создания синглтона, который используется в самом популярном ответе здесь (на момент написания).

С помощью методов класса Objective C мы можем просто избежать использования одноэлементного шаблона обычным способом:

[[Librarian sharedInstance] openLibrary]

to:

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

Если синглтон уже инициализирован, блок блокировки не будет введен. Вторая проверка, если(!initialized) - это убедиться, что он еще не инициализирован, когда текущий поток получает блокировку.

Я не прочитал все решения, поэтому простите, если этот код избыточен.

Это наиболее потокобезопасная реализация на мой взгляд.

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

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

    return sharedResourcesObj;
}

Я обычно использую код, примерно похожий на то, что в ответ Бен Hoffstein (который я также получил из Википедии). Я использую его по причинам, изложенным Крисом Хэнсоном в его комментарии.

однако иногда мне нужно поместить синглтон в перо, и в этом случае я использую следующее:

@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 (etc.) к читателю, хотя приведенный выше код-это все, что вам нужно в среде сборки мусора.

принятый ответ, хотя он компилируется, неверен.

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

в документации Apple:

... Вы можете использовать аналогичный подход для синхронизации методов класса связанного класса, используя объект класса вместо self.

даже если использование self работает, это не должно, и это похоже на ошибку копирования и вставки для меня. Правильная реализация для метода фабрики класса будет:

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