Как должен выглядеть мой синглтон Objective-C?

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

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

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 действует как барьер записи для потока настройки и барьер чтения из потока тестирования.

статический MyClass * sharedInst = nil;

+ (id) sharedInstance
{
    @synchronize (self) {
        if (sharedInst == nil) {
            / * sharedInst настроен в init */
            [[self alloc] init];
        }
    }
    вернуть sharedInst;
}

- (id) init
{
    if (sharedInst! = nil) {
        [Повышение NSException:NSInternalInconsistencyException
            формат:@"[%@ %@] не может быть вызван; вместо этого используйте +[%@ %@]"],
            NSStringFromClass([self class]), NSStringFromSelector(_cmd), 
            NSStringFromClass([собственный класс]),
            NSStringFromSelector(@selector(sharedInstance)"];
    } else if ( self = [super init]) {
        sharedInst = self;
        /* Какой бы класс не был здесь */
    }
    вернуть sharedInst;
}

/ * Они, вероятно, ничего не делают в
   приложение GC. Держит синглтон
   как фактический синглтон в
   не CG приложение
*/
- (NSUInteger)retainCount
{
    вернуть NSUIntegerMax;
}

- (одностороннее освобождение) выпуск
{
}

- (id) сохранить
{
    вернуть sharedInst;
}

- (id) авто-релиз
{
    вернуть 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,

Вы можете подумать, что вам следует неявно проверять дубликат инициализации в вашем блоке инициализации следующим образом:

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

У меня есть интересная вариация на 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

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

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

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

Singleton.m:

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

Как насчет

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

То есть вы избегаете затрат на синхронизацию после инициализации?

Вот макрос, который я собрал:

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

Это основано на работе здесь Мэтта Галлахера. Но изменив реализацию, чтобы использовать метод Swizzling, как описано здесь Дейвом МакЛахланом из Google.

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

Не должно ли это быть поточно-ориентированным и избежать дорогостоящей блокировки после первого звонка?

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

Это работает и в среде без сбора мусора.

@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

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

Использование шаблона Singleton в Objective-C

KLSingleton- это:

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

Чтобы расширить пример из @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;
}

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

Вот макросы, которые я написал на основе нескольких реализаций 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 для создания синглтона, который используется в самом популярном ответе здесь (на момент написания).

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

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

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

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

    return sharedResourcesObj;
}

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

[[Librarian sharedInstance] openLibrary]

чтобы:

[Librarian openLibrary]

оборачивая класс в другой класс, который имеет только методы класса, таким образом, нет никакой возможности случайно создать дубликаты экземпляров, так как мы не создаем ни одного экземпляра!

Я написал более подробный блог здесь:)

Просто хотел оставить это здесь, чтобы я не потерял это. Преимущество этого в том, что его можно использовать в 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

Мой способ прост, как это:

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) должна убедиться, что она еще не инициализирована, когда текущий поток получает LOCK.

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

Однако иногда мне нужно поместить синглтон в 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;
}

Согласно документации Apple:

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

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

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}
Другие вопросы по тегам