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


334

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

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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


27
То, что у вас есть, хорошо, хотя вы можете переместить объявление глобальной переменной в метод + instance (единственное место, где его нужно использовать, если только вы не разрешаете его устанавливать), и использовать имя типа + defaultMyClass или + sharedMyClass для вашего метода. + экземпляр не раскрывает намерения.
Крис Хэнсон

Поскольку маловероятно, что «ответ» на этот вопрос изменится в ближайшее время, я ставлю историческую блокировку на вопрос. Две причины: 1) Множество просмотров, голосов и хорошего контента. 2) Предотвращение открытия / закрытия. Это был большой вопрос для своего времени, но вопросы такого типа не подходят для переполнения стека. Теперь у нас есть Code Review для проверки рабочего кода. Пожалуйста, отнесите все обсуждения этого вопроса к этому мета-вопросу .
Джордж Стокер

Ответы:


207

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

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

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

static MySingleton *sharedSingleton;

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

7
Если среда выполнения вызовет его только один раз, что делает BOOL? Является ли это предосторожностью в случае, если кто-то вызывает эту функцию явно из своего кода?
Aftermathew

5
Да, это предосторожность, так как функция также может быть вызвана напрямую.
Робби Хэнсон

33
Это также необходимо, потому что могут быть подклассы. Если они не переопределяют, +initializeих суперклассовая реализация будет вызвана, если подкласс используется впервые.
Свен

3
@Paul вы можете переопределить releaseметод и сделать его пустым. :)

4
@aryaxt: из перечисленных документов это уже потокобезопасно. Таким образом, вызов происходит один раз за время выполнения. Казалось бы, это правильное, поточно-ориентированное, оптимально эффективное решение.
lilbyrdie

95
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

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

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

    return sharedSingleton;
  }
}

@end

[Источник]


7
Это все, что вы обычно должны использовать для одиночных игр. Помимо прочего, хранение экземпляров ваших классов по отдельности облегчает их тестирование, поскольку вы можете тестировать отдельные экземпляры вместо того, чтобы иметь возможность сбросить их состояние.
Крис Хэнсон

3
Стиг Браутасет: Нет, в этом примере нельзя игнорировать @synchronized. Он предназначен для обработки возможного состояния состязания двух потоков, одновременно выполняющих эту статическую функцию, которые одновременно проходят тест «if (! SharedSingleton)» и в результате получают два [MySingleton alloc] s. .. @synchronized {scope block} заставляет этот гипотетический второй поток ждать, пока первый поток выйдет из {scope block}, прежде чем ему будет разрешено войти в него. Надеюсь, это поможет! =)
MechEthan

3
Что мешает кому-то сделать свой экземпляр объекта? MySingleton *s = [[MySingelton alloc] init];
Линдон Фокс

1
@lindonfox Каков ответ на ваш вопрос?
Раффи Хачадурян

1
@Raffi - извините, я думаю, что забыл вставить свой ответ. Во всяком случае, я получил книгу, Pro Objective-C Design Patterns for iOSи в ней объясняется, как вы делаете "строгий" Singelton. По сути, поскольку вы не можете сделать инициализирующие методы частными, вам необходимо переопределить методы alloc и copy. Поэтому, если вы попытаетесь сделать что-то подобное, [[MySingelton alloc] init]вы получите ошибку во время выполнения (но, к сожалению, не во время компиляции). Я не понимаю, как все детали создания объекта, но вы реализуете, + (id) allocWithZone:(NSZone *)zoneчто называетсяsharedSingleton
Линдон Фокс

59

На мой другой ответ ниже, я думаю, что вы должны делать:

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

6
Не беспокойтесь обо всем, что вы делаете выше. Сделайте ваши (надеюсь, очень мало) синглтоны по отдельности инстанцируемыми, и просто используйте метод shared / default. То, что вы сделали, необходимо, только если вы действительно, действительно, хотите ТОЛЬКО один экземпляр вашего класса. Что вы не делаете, особенно для юнит-тестов.
Крис Хэнсон

Дело в том, что это пример кода Apple для «создания синглтона». Но да, ты абсолютно прав.
Колин Барретт

1
Пример кода Apple верен, если вы хотите «истинный» синглтон (т.е. объект, который может быть создан только один раз, когда-либо), но, как говорит Крис, это редко то, что вы хотите или нуждаетесь, тогда как какой-то настраиваемый общий экземпляр - это то, что вам нужно обычно хочу.
Люк Редпат

Вот макрос для вышеуказанного метода: gist.github.com/1057420 . Это то, что я использую.
Кобски

1
Если не считать модульных тестов, против этого решения ничего не говорится, верно? И это быстро и безопасно.
LearnCocos2D

58

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

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

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

Хорошо, позвольте мне объяснить, как это работает:

  1. Быстрый случай: в нормальном исполнении sharedInstanceуже был установлен, поэтому whileцикл никогда не выполняется, и функция возвращается после простого тестирования на существование переменной;

  2. Медленный случай: если sharedInstanceне существует, то экземпляр выделяется и копируется в него с помощью Compare And Swap ('CAS');

  3. Спорный случай: если две нити и попытка вызова sharedInstanceв то же время И sharedInstance не существует в то же время , то они оба инициализации новых экземпляры одноточечного и попытаться CAS его в положение. Независимо от того, кто выиграл, CAS немедленно возвращается, а тот, который проиграл, освобождает только что выделенный экземпляр и возвращает (теперь установлено) sharedInstance. Сингл OSAtomicCompareAndSwapPtrBarrierдействует как барьер записи для потока настройки и барьер чтения из потока тестирования.


18
Это полное излишество, по крайней мере, один раз, когда это может произойти за время существования приложения. Тем не менее, он точен, и техника сравнения и обмена - полезный инструмент, о котором нужно знать, так что +1.
Стив Мэдсен

Хороший ответ - о семействе OSAtomic полезно знать
Билл

1
@Louis: Удивительный, действительно поучительный ответ! Хотя один вопрос: что мой initметод должен делать в вашем подходе? sharedInstanceЯ полагаю, что генерировать исключение при инициализации не очень хорошая идея. Что делать, чтобы пользователь не звонил initнапрямую много раз?
MATM

2
Я вообще не мешаю этому. Часто существуют веские причины, чтобы разрешить многократное создание экземпляров того, что обычно является синглтоном, при этом наиболее распространенным является использование определенных типов модульного тестирования. Если бы я действительно хотел обеспечить выполнение одного экземпляра, я бы, вероятно, проверил метод init, чтобы увидеть, существует ли глобальный объект, и если он это сделал, я высвободил self и возвратил глобальный.
Луи Гербарг

1
@ Тони немного опоздал с ответом, но OSAtomicCompareAndSwapPtrBarrier требует энергозависимости. Возможно, ключевое слово volatile - чтобы компилятор не оптимизировал проверку? См .: stackoverflow.com/a/5334727/449161 и developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Бен Флинн

14
статический 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;
}

3
Я заметил, что clang жалуется на утечку, если вы не присваиваете результат [[self alloc] init]sharedInst.
pix0r

Подрывать init, как это, довольно уродливый подход IMO. Не связывайтесь с init и / или фактическим созданием объекта. Если вместо этого вы выберете контролируемую точку доступа к общему экземпляру, а не запекаете синглтон в объект, у вас будет больше времени спустя, если вы будете писать тесты и т. Д. Сложные синглтоны слишком перегружены.
Occulus

12

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


1
Я бы просто жил проще и вообще не стал бы инициализироваться.
Том Андерсен


9

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

1
+1 это действительно интригует. Я мог бы использовать, class_replaceMethodчтобы превратиться sharedInstanceв клона simpleSharedInstance. Таким образом, вам никогда не придется беспокоиться о получении @synchronizedблокировки снова.
Дейв Делонг

Это тот же эффект, использование exchangeImplementations означает, что после init, когда вы вызываете sharedInstance, вы действительно вызываете simpleSharedInstance. На самом деле я начал с replaceMethod, но решил, что лучше просто поменять реализации так, чтобы оригинал все еще существовал при необходимости ...
Кендалл Хельмштеттер Гелнер

В дальнейшем тестировании я не смог заставить replaceMethod работать - при повторных вызовах код все еще называл исходный sharedInstance вместо simpleSharedInstance. Я думаю, что это может быть потому, что оба они являются методами уровня класса ... Я использовал замену: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); и некоторые их варианты. Я могу проверить код, который я опубликовал, работает, и simpleSharedInstance вызывается после первого прохода через sharedInstance.
Кендалл Хельмштеттер Гелнер

Вы можете сделать поточно-ориентированную версию, которая не окупит затраты на блокировку после инициализации, не выполняя кучу хакерских атак во время выполнения. Я разместил реализацию ниже.
Луи Гербарг

1
+1 отличная идея. Мне просто нравятся те вещи, которые можно делать во время выполнения. Но в большинстве случаев это, вероятно, преждевременная оптимизация. Если бы мне действительно пришлось избавиться от стоимости синхронизации, я бы, вероятно, использовал версию без блокировки Луи.
Свен

6

Краткий ответ: сказочный.

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

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

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

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

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

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

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


5

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

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


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

2

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

@interface MySingleton : NSObject {
}

+(MySingleton *)sharedManager;

@end


@implementation MySingleton

static MySingleton *sharedMySingleton = nil;

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


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


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

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


-(id)retain {
    return self;
}


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


-(void)release {
    //do nothing    
}


-(id)autorelease {
    return self;    
}


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

    //initialize here

    return self;
}

@end

2

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

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

2
Используемая здесь технология двойной проверки блокировки часто является реальной проблемой в некоторых средах (см. Aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf или Google it). Пока не показано иное, я бы предположил, что Objective-C не застрахован. Также см. Wincent.com/a/knowledge-base/archives/2006/01/… .
Стив Мэдсен

2

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

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

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

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


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

2

Как насчет

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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


См. Обсуждение двойной проверенной блокировки в других ответах.
i_am_jorf


1

KLSingleton это:

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

KLSingleton


1
Я использую ваш NSSingleton для своего проекта, и он кажется несовместимым с KVO. Дело в том, что KVO создает подкласс для каждого объекта KVO с префиксом NSKVONotifying_ MyClass . И это заставляет MyClass + инициализировать и -init методы вызываться дважды.
Олег Трахман

Я проверил это на последнем Xcode и не испытывал проблем при регистрации или получении событий KVO. Вы можете проверить это с помощью следующего кода: gist.github.com/3065038 Как я уже упоминал в Twitter, методы + initialize вызываются один раз для NSSingleton и один раз для каждого подкласса. Это свойство Objective-C.
Кевинлаулер

Если вы добавите NSLog(@"initialize: %@", NSStringFromClass([self class]));в +initializeметод, вы можете проверить, что классы инициализируются только один раз.
Кевинлаулер

NSLog (@ "initialize:% @", NSStringFromClass ([self class]));
Олег Трахман

Возможно, вы также захотите, чтобы он был совместим с IB. Шахта: stackoverflow.com/questions/4609609/…
Дэн Розенстарк

0

Вы не хотите синхронизироваться с самим собой ... Так как сам объект еще не существует! Вы в конечном итоге блокируете временное значение идентификатора. Вы хотите, чтобы никто другой не мог запускать методы класса (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

1
Остальные методы, методы доступа, методы-мутаторы и т. Д. Должны синхронизироваться самостоятельно. Все методы класса (+) и инициализаторы (и, вероятно, -dealloc) должны синхронизироваться с объектом класса. Вы можете избежать ручной синхронизации, если используете методы Objective-C 2.0 вместо методов доступа или мутатора. Все object.property и object.property = foo автоматически синхронизируются с самим собой.
Роб Дотсон

3
Пожалуйста, объясните, почему вы думаете, что selfобъект не существует в методе класса. Среда выполнения определяет, какую реализацию метода вызывать, основываясь на том же значении, которое она предоставляет selfдля каждого метода (класса или экземпляра).
Dreamlax

2
Внутри метода класса self находится объект класса. Попробуйте сами:#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
ОАО

0

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

0
static mySingleton *obj=nil;

@implementation mySingleton

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

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

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

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

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

- (id)autorelease {
    return self;
}

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

0

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

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

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

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


0

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

[[Librarian sharedInstance] openLibrary]

чтобы:

[Librarian openLibrary]

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

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


Ваша ссылка больше не работает.
i_am_jorf

0

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

0

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

static id instanceOfXXX = nil;

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

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

    return instanceOfXXX;
}

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


Не понятно, что маркировка initializedтак volatileже достаточна. См. Aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf

0

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

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

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

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

    return sharedResourcesObj;
}

-4

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

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


2
Ваш код не является потокобезопасным. Он использует синхронизированный в методе alloc, но не в методе init. Проверка инициализированного bool не является поточно-ориентированным.
Меки

-5

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

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

6
Я, безусловно , существует в этом классе. Это относится к классу, а не к экземпляру класса. Классы - это (в основном) объекты первого класса.
Шва

Почему вы положили @synchroninzed WITHIN метод?
user4951

1
Как уже сказал Шва, self это объект класса внутри метода класса. Смотрите мой комментарий для фрагмента, демонстрирующего это.
ОАО

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.
неприветливо
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.