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

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

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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


person schwa    schedule 28.09.2008    source источник
comment
То, что у вас есть, в порядке, хотя вы можете переместить объявление глобальной переменной в свой метод экземпляра + (единственное место, где его нужно использовать, если вы не разрешаете его также устанавливать) и использовать имя вроде + defaultMyClass или + sharedMyClass для вашего метода. + экземпляр не раскрывает намерения.   -  person Chris Hanson    schedule 28.09.2008
comment
Поскольку маловероятно, что «ответ» на этот вопрос изменится в ближайшее время, я помещаю этот вопрос в историческую блокировку. Две причины: 1) Большое количество просмотров, голосов и хороший контент 2) Чтобы не допустить открытия / закрытия. Это был отличный вопрос для своего времени, но вопросы такого типа не подходят для Stack Overflow. Теперь у нас есть проверка кода для проверки рабочего кода. Перенесите все обсуждения этого вопроса на этот мета-вопрос.   -  person George Stocker    schedule 21.03.2013


Ответы (25)


Другой вариант - использовать метод +(void)initialize. Из документации:

Среда выполнения отправляет initialize каждому классу в программе ровно один раз непосредственно перед тем, как класс или любой наследующий от него класс отправит свое первое сообщение из программы. (Таким образом, метод никогда не может быть вызван, если класс не используется.) Среда выполнения отправляет сообщение initialize классам потокобезопасным способом. Суперклассы получают это сообщение раньше своих подклассов.

Итак, вы можете сделать что-нибудь похожее на это:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}
person Robbie Hanson    schedule 05.12.2008
comment
Если среда выполнения вызовет это только один раз, что делает BOOL? Это мера предосторожности на случай, если кто-то явно вызовет эту функцию из своего кода? - person Aftermathew; 03.04.2009
comment
Да, это мера предосторожности, поскольку функция также может быть вызвана напрямую. - person Robbie Hanson; 08.04.2009
comment
Это также требуется, потому что могут быть подклассы. Если они не переопределяют +initialize, их реализация суперкласса будет вызываться при первом использовании подкласса. - person Sven; 07.09.2010
comment
Что произойдет, если кто-то непреднамеренно освободит этот экземпляр? Они эффективно освобождают его для всего процесса. Я думаю, что стандартный метод, который переопределяет методы копирования / выпуска / автоматического выпуска, более надежен. - person Paul Alexander; 23.10.2010
comment
Я сам предпочитаю метод инициализации. Таким образом, я могу написать простой метод getSingleton, который может просто возвращать ссылку на синглтон. Но почему ваш метод инициализации не может просто проверить одноэлементную ссылку на nil вместо установки другого логического значения? - person Marvo; 30.01.2011
comment
Да, он может просто проверить, равно ли значение sharedSingleton нулю. Это просто шаблонный код, который позволит вам делать другие вещи в методе инициализации, например, инициализировать другие статические переменные. - person Robbie Hanson; 25.03.2011
comment
@Paul, вы можете переопределить метод release и сделать его пустым. :) - person ; 26.03.2011
comment
Ага, это ужасно, просто используйте Synchronized, что делает блок кода доступным для одного потока за раз. - person aryaxt; 01.04.2011
comment
@aryaxt: Судя по перечисленным документам, это уже потокобезопасно. Итак, вызов выполняется один раз за период выполнения. Казалось бы, это правильное, поточно-ориентированное и оптимально эффективное решение. - person lilbyrdie; 23.06.2011
comment
comment
@JJD: Пример Apple предназначен для создания прозрачно строгих синглтонов, что почти никогда не подходит. Большинство синглтонов предназначены для удобства, а не для требований (их может быть несколько, обычно их нет; например, NSNotificationCenter). Для классов, которые должны быть одиночными, создание нескольких экземпляров обычно является ошибкой программирования, и вам следует утверждать в init, а не исправлять ошибку вызывающего. Единственный раз, когда пример Apple имеет смысл, - это когда синглтон представляет собой деталь реализации, которую вызывающий может игнорировать. Как минимум, класс должен быть неизменным. - person Rob Napier; 09.09.2011
comment
В приведенном выше примере - почему функция init на самом деле не вызывается для объекта? Например, в моем одноэлементном классе у меня была старая реализация, не ориентированная на потоки, и я пытаюсь ее исправить. Операторы журнала показывают, что при реализации функции инициализации, как указано выше, функция инициализации - (id) класса singleton никогда не вызывается. - person ch3rryc0ke; 11.04.2012
comment
ИМХО инициализация довольно бесполезна. У вас очень мало контроля над тем, когда (например, порядок может быть важен, но вы не понимаете этого, пока он не изменится на вас). Также проблема подкласса ... Также вам нужно несколько методов для работы с синглтоном .. - person Tom Andersen; 17.05.2012
comment
Вы также можете использовать self вместо MySingleton, это избавит от необходимости создавать подклассы + (void)initialize в подклассах. = ›sharedSingleton = [[self alloc] init]; - person IluTov; 23.12.2012
comment
Обратите внимание, что +initialize вызывается при первой отправке сообщения классу или подклассу. Так, в частности, если вы попытаетесь получить доступ к sharedSingleton без вызова каких-либо методов класса, вы получите nil, если сначала не отправите фиктивное сообщение (например, (void)[MySingleton class];). - person Adam Rosenfield; 25.12.2012

@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

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

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

    return sharedSingleton;
  }
}

@end

[Источник]

person Ben Hoffstein    schedule 28.09.2008
comment
Это все, что вы обычно должны использовать для одиночных игр. Помимо прочего, сохранение экземпляров ваших классов по отдельности упрощает их тестирование, поскольку вы можете тестировать отдельные экземпляры вместо того, чтобы иметь возможность сбросить их состояние. - person Chris Hanson; 28.09.2008
comment
Можно ли не использовать @syncronised, если вы не храните какие-либо данные в синглтоне? - person Stig Brautaset; 12.05.2011
comment
Стиг Браутасет: Нет, в этом примере нельзя опускать @synchronized. Он предназначен для обработки возможного состояния гонки двух потоков, выполняющих эту статическую функцию в одно и то же время, причем оба одновременно проходят проверку if (! SharedSingleton) и, таким образом, приводят к двум [MySingleton alloc] ... @Synchronized {блок области} заставляет этот гипотетический второй поток ждать, пока первый поток не выйдет из {блока области}, прежде чем ему будет разрешено перейти в него. Надеюсь, это поможет! знак равно - person MechEthan; 13.07.2011
comment
Что мешает кому-то по-прежнему создавать собственный экземпляр объекта? MySingleton *s = [[MySingelton alloc] init]; - person lindon fox; 28.10.2011
comment
Извините, отвечая на мой вопрос ... - person lindon fox; 28.10.2011
comment
@lindonfox Каков ответ на ваш вопрос? - person Raffi Khatchadourian; 21.12.2011
comment
Интересно, как self доступен в методе класса. Я предполагаю, что self относится к экземпляру класса. Это правильно? - person Raffi Khatchadourian; 21.12.2011
comment
@Raffi - извините, я, должно быть, забыл вставить свой ответ. Как бы то ни было, у меня есть книга Pro Objective-C Design Patterns for iOS, в которой объясняется, как сделать строгий синглтон. В основном, поскольку вы не можете сделать методы запуска закрытыми, вам необходимо переопределить методы выделения и копирования. Поэтому, если вы попытаетесь сделать что-то вроде [[MySingelton alloc] init], вы получите ошибку времени выполнения (но, к сожалению, не ошибку времени компиляции). Я не понимаю, как все детали создания объекта, но вы реализуете + (id) allocWithZone:(NSZone *)zone, который вызывается в sharedSingleton - person lindon fox; 21.12.2011
comment
Хотя это решение правильное, использование @synchronized беспокоит меня, поскольку ужасно медленное. - person DarkDust; 17.04.2012

В соответствии с моим другим ответом ниже, я думаю, вам следует сделать:

+ (id)sharedFoo
{
    static dispatch_once_t once;
    static MyFoo *sharedFoo;
    dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
    return sharedFoo;
}
person Colin Barrett    schedule 28.09.2008
comment
Не беспокойтесь обо всем, что вы делаете выше. Сделайте ваши синглтоны (надеюсь, очень немного) инстанцируемыми по отдельности и просто используйте общий метод / метод по умолчанию. То, что вы сделали, необходимо только в том случае, если вам действительно действительно нужен ТОЛЬКО один экземпляр вашего класса. Чего вы не делаете, особенно. для модульных тестов. - person Chris Hanson; 28.09.2008
comment
Дело в том, что это пример кода Apple для создания синглтона. Но да, ты абсолютно прав. - person Colin Barrett; 23.10.2008
comment
Пример кода Apple верен, если вам нужен настоящий синглтон (то есть объект, который может быть создан только один раз, когда-либо), но, как говорит Крис, это редко то, что вам нужно или нужно, тогда как какой-то настраиваемый общий экземпляр - это то, что вы обычно хотите . - person Luke Redpath; 25.07.2009
comment
Вот макрос для вышеуказанного метода: gist.github.com/1057420. Это то, что я использую. - person Kobski; 30.01.2012
comment
Помимо модульных тестов, ничего не говорит против этого решения, верно? И это быстро и безопасно. - person LearnCocos2D; 07.04.2012

Поскольку Кендалл опубликовал потокобезопасный синглтон, который пытается Чтобы избежать затрат на блокировку, я подумал, что тоже подброшу:

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

person Louis Gerbarg    schedule 15.03.2010
comment
Это полное излишество, самое большее, что может случиться в течение жизненного цикла приложения. Тем не менее, это абсолютно верно, и метод сравнения и обмена - полезный инструмент, о котором нужно знать, так что +1. - person Steve Madsen; 22.04.2010
comment
Хороший ответ - семейство OSAtomic - это хорошая вещь, о которой нужно знать - person Bill; 27.02.2011
comment
@Louis: Удивительный, действительно поучительный ответ! Один вопрос: что должен делать мой init метод в вашем подходе? Я считаю, что создание исключения при инициализации sharedInstance - не лучшая идея. Что делать, чтобы пользователь не звонил init напрямую много раз? - person matm; 01.09.2011
comment
Я вообще не препятствую этому. Часто существуют веские причины для того, чтобы разрешить умножение экземпляров того, что обычно является синглтоном, чаще всего для определенных типов модульного тестирования. Если бы я действительно хотел принудительно применить один экземпляр, я бы, вероятно, проверил метод init, чтобы увидеть, существует ли глобальный объект, и если бы это было так, я бы выпустил self и вернул глобальный. - person Louis Gerbarg; 02.09.2011
comment
Хм, а зачем здесь нужна volatile квалификация? Поскольку sharedInstance инициализируется только один раз, почему мы не позволяем компилятору кэшировать его в регистре с помощью volatile? - person Tony; 29.12.2011
comment
@Tony немного опоздал с ответом, но OSAtomicCompareAndSwapPtrBarrier требует volatile. Возможно, ключевое слово volatile состоит в том, чтобы не дать компилятору оптимизировать проверку? См .: stackoverflow.com/a/5334727/449161 и developer.apple.com/library/mac/#documentation/Darwin/Reference/ - person Ben Flynn; 16.11.2012

Изменить: эта реализация устарела с 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.)

person lorean    schedule 07.06.2011
comment
Я бы просто жил попроще и вообще избегал инициализации. - person Tom Andersen; 17.05.2012

Подробное объяснение кода макроса Singleton можно найти в блоге Cocoa With Love.

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

person Matthieu Cormier    schedule 23.06.2009

У меня есть интересный вариант 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;
}
person Kendall Helmstetter Gelner    schedule 19.02.2010
comment
+1 это действительно интригует. Я мог бы использовать class_replaceMethod, чтобы преобразовать sharedInstance в клон simpleSharedInstance. Таким образом, вам больше не придется беспокоиться о том, чтобы снова получить @synchronized блокировку. - person Dave DeLong; 19.02.2010
comment
Тот же эффект, использование exchangeImplementations означает, что после инициализации, когда вы вызываете sharedInstance, вы действительно вызываете simpleSharedInstance. На самом деле я начал с replaceMethod, но решил, что лучше просто переключить реализации, чтобы оригинал все еще существовал, если это необходимо ... - person Kendall Helmstetter Gelner; 19.02.2010
comment
При дальнейшем тестировании мне не удалось заставить replaceMethod работать - при повторных вызовах код по-прежнему вызывал исходный sharedInstance вместо simpleSharedInstance. Я думаю, это может быть потому, что они оба являются методами уровня класса ... Я использовал замену: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); и некоторые его вариации. Я могу проверить опубликованный мной код, и simpleSharedInstance вызывается после первого прохода через sharedInstance. - person Kendall Helmstetter Gelner; 19.02.2010
comment
Вы можете создать потокобезопасную версию, которая не оплачивает затраты на блокировку после инициализации, не выполняя кучу очистки времени выполнения, я опубликовал реализацию ниже. - person Louis Gerbarg; 15.03.2010
comment
+1 отличная идея. Мне просто нравится то, что можно делать со средой выполнения. Но в большинстве случаев это, вероятно, преждевременная оптимизация. Если бы мне действительно пришлось избавиться от затрат на синхронизацию, я бы, вероятно, использовал безблокировочную версию Луи. - person Sven; 07.09.2010
comment
Время шло, и теперь у Apple есть лучший способ настроить одиночные игры с помощью GCD ... так что другие могут захотеть это найти. Конечно, если вам все равно нужно поддерживать вещи до iOS4 ... - person Kendall Helmstetter Gelner; 22.06.2011
comment
@KendallHelmstetterGelner, вы имеете в виду dispatch_once? - person i_am_jorf; 07.03.2013
comment
Да, точно, dispatch_once. Это то, что я использую почти все время, если мне нужен синглтон. - person Kendall Helmstetter Gelner; 07.03.2013

Краткий ответ: потрясающе.

Длинный ответ: что-то вроде ....

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

person quellish    schedule 08.02.2012

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

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

person obscenum    schedule 30.11.2010
comment
Возможно, вы захотите использовать какой-либо другой механизм блокировки, потому что @synchronized ужасно медленный, и его следует избегать. - person DarkDust; 17.04.2012

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

@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
person lajos    schedule 28.09.2008

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

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}
person Jompe    schedule 17.03.2010
comment
Используемая здесь методика блокировки с двойной проверкой часто является реальной проблемой в некоторых средах (см. aristeia.com/ Papers / DDJ_Jul_Aug_2004_revised.pdf или Google). Пока не будет показано иное, я предполагаю, что Objective-C не застрахован. См. Также wincent.com/a/knowledge-base/ archives / 2006/01 /. - person Steve Madsen; 22.04.2010

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

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

Он основан на работе Мэтта Галлахера Но изменение реализации на использование метода swizzling, как описано здесь Дейвом Маклахланом Google.

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

person CJ Hanson    schedule 02.03.2010
comment
ссылка кажется неработающей - где я могу взять этот источник? - person amok; 30.08.2010

Как насчет

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

person Tony    schedule 29.12.2011
comment
См. Обсуждение Блокировка с двойной проверкой в ​​других ответах. - person i_am_jorf; 07.03.2013

Подробное обсуждение шаблона singleton в Objective-C можно найти здесь:

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

person Fred McCann    schedule 21.05.2010

KLSingleton - это:

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

KLSingleton

person kevinlawler    schedule 03.05.2012
comment
Я использую ваш NSSingleton для своего проекта, и он, похоже, несовместим с KVO. Дело в том, что KVO создает подкласс для каждого объекта KVO с префиксом NSKVONotifying_ MyClass. И это заставляет методы MyClass + initialize и -init вызываться дважды. - person Oleg Trakhman; 03.07.2012
comment
Я тестировал это на последней версии Xcode, и у меня не было проблем с регистрацией или получением событий KVO. Вы можете проверить это с помощью следующего кода: gist.github.com/3065038 Как я уже упоминал в Твиттере, методы + initialize вызываются один раз для NSSingleton и один раз для каждого подкласса. Это свойство Objective-C. - person kevinlawler; 07.07.2012
comment
Если вы добавите NSLog(@"initialize: %@", NSStringFromClass([self class])); к методу +initialize, вы можете убедиться, что классы инициализируются только один раз. - person kevinlawler; 07.07.2012
comment
NSLog (@initialize:% @, NSStringFromClass ([собственный класс])); - person Oleg Trakhman; 07.07.2012
comment
Возможно, вы захотите, чтобы он был совместим с IB. Мой: stackoverflow.com/questions/4609609/ - person Dan Rosenstark; 05.09.2012

Вы не хотите синхронизировать на себе ... Поскольку сам объект еще не существует! В конечном итоге вы блокируете временное значение идентификатора. Вы хотите убедиться, что никто другой не может запускать методы класса (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
person Community    schedule 13.01.2010
comment
Остальные методы, методы доступа, методы мутатора и т. Д. Должны синхронизироваться самостоятельно. Все методы и инициализаторы класса (+) (и, вероятно, -dealloc) должны синхронизироваться с объектом класса. Вы можете избежать ручной синхронизации, если используете свойства Objective-C 2.0 вместо методов доступа / мутатора. Все object.property и object.property = foo автоматически синхронизируются с self. - person Rob Dotson; 14.01.2010
comment
Пожалуйста, объясните, почему вы думаете, что объект self не существует в методе класса. Среда выполнения определяет, какую реализацию метода вызывать, основываясь на том же самом значении, которое она предоставляет как self для каждого метода (класса или экземпляра). - person dreamlax; 19.02.2010
comment
Внутри метода класса 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; } - person jscs; 27.09.2011

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

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

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

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

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

person Nate    schedule 22.11.2011

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

[[Librarian sharedInstance] openLibrary]

to:

[Librarian openLibrary]

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

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

person chunkyguy    schedule 10.02.2012
comment
Ваша ссылка больше не работает. - person i_am_jorf; 07.03.2013

Чтобы продолжить пример из @ 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;
}
person JJD    schedule 22.03.2012

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

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.

person TienDC    schedule 23.12.2012
comment
Неясно, достаточно ли пометки initialized как volatile. См. aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf. - person i_am_jorf; 07.03.2013

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

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

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

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

    return sharedResourcesObj;
}
person Zolt    schedule 19.02.2013

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

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

person Gregory Higley    schedule 24.06.2009
comment
Ваш код не является потокобезопасным. Он использует synchronized в методе alloc, но не в методе init. Проверка инициализированного bool не является потокобезопасным. - person Mecki; 29.06.2009

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

+ (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;
}
person deleted_user    schedule 23.01.2011
comment
self, безусловно, действительно существует в области класса. Он относится к классу, а не к экземпляру класса. Классы (в основном) являются объектами первого класса. - person schwa; 25.01.2011
comment
Почему вы помещаете @synchroninzed ВНУТРИ метода? - person user4951; 04.05.2011
comment
Как уже было сказано, self - это объект класса внутри метода класса. фрагмент см. в моем комментарии демонстрируя это. - person jscs; 27.09.2011
comment
self существует, но использование его в качестве идентификатора, переданного в @synchronized, синхронизирует доступ к методам экземпляра. Как указывает @ user490696, есть случаи (например, одиночные), когда использование объекта класса предпочтительнее. Из Руководства по программированию Obj-C: 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. - person quellish; 08.02.2012

person    schedule
comment
Я заметил, что clang жалуется на утечку, если вы не присваиваете результат [[self alloc] init] sharedInst. - person pix0r; 06.05.2009
comment
Подрыв init, подобный этому, - довольно уродливый подход, ИМО. Не связывайтесь с инициализацией и / или фактическим созданием объекта. Если вместо этого вы выберете контролируемую точку доступа к совместно используемому экземпляру, не запекая синглтоны в объекте жестко, то позже у вас будет больше счастья, если вы напишете тесты и т. Д. Жесткие синглтоны используются слишком часто. - person occulus; 14.01.2013