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

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

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

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

Chris Hanson 28.09.2008 13:37

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

George Stocker 21.03.2013 23:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
333
2
183 657
26
Перейти к ответу Данный вопрос помечен как решенный

Ответы 26

@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

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

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

    return sharedSingleton;
  }
}

@end

[Источник]

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

Chris Hanson 28.09.2008 13:36

Можно ли не указывать @syncronised, если вы не храните какие-либо данные в синглтоне?

Stig Brautaset 12.05.2011 19:10

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

MechEthan 13.07.2011 20:29

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

lindon fox 28.10.2011 09:31

Извините, отвечая на свой вопрос ...

lindon fox 28.10.2011 16:24

@lindonfox Каков ответ на ваш вопрос?

Raffi Khatchadourian 21.12.2011 01:48

Интересно, как self доступен в методе класса. Я предполагаю, что self относится к экземпляру класса. Это верно?

Raffi Khatchadourian 21.12.2011 02:57

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

lindon fox 21.12.2011 07:42

Хотя это решение правильное, использование @synchronized беспокоит меня как это ужасно медленно.

DarkDust 17.04.2012 12:00

static MyClass *sharedInst = nil;

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

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

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

- (oneway void)release
{
}

- (id)retain
{
    return sharedInst;
}

- (id)autorelease
{
    return sharedInst;
}

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

pix0r 06.05.2009 22:21

Подрыв init, подобный этому, - довольно уродливый подход ИМО. Не связывайтесь с инициализацией и / или фактическим созданием объекта. Если вместо этого вы выберете контролируемую точку доступа к совместно используемому экземпляру, не запекая синглтоны в объекте жестко, то позже у вас будет больше счастья, если вы напишете тесты и т. д. Жесткие синглтоны используются слишком часто.

occulus 14.01.2013 17:41

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

@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

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

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

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

Chris Hanson 28.09.2008 13:35

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

Colin Barrett 23.10.2008 05:39

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

Luke Redpath 25.07.2009 05:25

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

Kobski 30.01.2012 23:21

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

LearnCocos2D 07.04.2012 00:19
Ответ принят как подходящий

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

The runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses.

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

static MySingleton *sharedSingleton;

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

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

Aftermathew 03.04.2009 21:28

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

Robbie Hanson 08.04.2009 08:32

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

Sven 07.09.2010 01:25

Что произойдет, если кто-то непреднамеренно освободит этот экземпляр? Они фактически освобождают его для всего процесса. Я думаю, что стандартный метод, который переопределяет методы копирования / выпуска / автоматического выпуска, более надежен.

Paul Alexander 23.10.2010 18:18

Я сам предпочитаю метод инициализации. Таким образом, я могу написать простой метод getSingleton, который может просто возвращать ссылку на синглтон. Но почему ваш метод инициализации не может просто проверить ссылку singleton на nil вместо установки другого логического значения?

Marvo 30.01.2011 03:56

Да, он может просто проверить, равно ли значение sharedSingleton нулю. Это просто шаблонный код, который позволит вам делать другие вещи в методе инициализации, например, инициализировать другие статические переменные.

Robbie Hanson 25.03.2011 18:31

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

user142019 26.03.2011 01:20

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

aryaxt 01.04.2011 04:31

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

lilbyrdie 23.06.2011 17:52

А что насчет Создание экземпляра Singleton в документации?

JJD 29.08.2011 17:23

@JJD: Пример Apple предназначен для создания прозрачно строгих синглтонов, что почти никогда не подходит. Большинство синглтонов предназначены для удобства, а не для требований (их может быть несколько, обычно их нет; например, NSNotificationCenter). Для классов, которые должен являются одиночными, создание нескольких экземпляров обычно является ошибкой программирования, и вам следует утверждать в init, а не «исправлять» ошибку вызывающего. Единственный раз, когда пример Apple имеет смысл, - это когда синглтон - это деталь реализации, которую вызывающий может игнорировать. Как минимум, класс должен быть неизменным.

Rob Napier 09.09.2011 18:45

В приведенном выше примере - почему функция init на самом деле не вызывается для объекта? Например, в моем одноэлементном классе у меня была старая реализация, не ориентированная на потоки, и я пытаюсь ее исправить. Операторы журнала показывают, что при реализации функции инициализации, как указано выше, функция инициализации - (id) класса singleton никогда не вызывается.

ch3rryc0ke 11.04.2012 03:13

ИМХО инициализация довольно бесполезна. У вас очень мало контроля над тем, когда (например, порядок может быть важен, но вы не понимаете этого, пока он не изменится на вас). Также проблема подкласса ... Также вам нужно несколько методов для работы с синглтоном ..

Tom Andersen 17.05.2012 17:20

Вы также можете использовать self вместо MySingleton, это избавит от необходимости создавать подклассы + (void)initialize в подклассах. => sharedSingleton = [[self alloc] init];

IluTov 23.12.2012 22:33

Обратите внимание, что +initialize вызывается при первой отправке сообщения классу или подклассу. Так, в частности, если вы попытаетесь получить доступ к sharedSingleton без вызова каких-либо методов класса, вы получите nil, если сначала не отправите фиктивное сообщение (например, (void)[MySingleton class];).

Adam Rosenfield 25.12.2012 01:36

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

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

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

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

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

Mecki 29.06.2009 16:44

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

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

Rob Dotson 14.01.2010 01:10

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

dreamlax 19.02.2010 09:53

Внутри метода класса 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; }

jscs 27.09.2011 06:23

У меня есть интересный вариант 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 это действительно интригует. Я мог бы использовать class_replaceMethod для преобразования sharedInstance в клон simpleSharedInstance. Таким образом, вам больше никогда не придется беспокоиться о приобретении замка @synchronized.

Dave DeLong 19.02.2010 09:19

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

Kendall Helmstetter Gelner 19.02.2010 10:46

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

Kendall Helmstetter Gelner 19.02.2010 10:58

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

Louis Gerbarg 15.03.2010 21:57

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

Sven 07.09.2010 01:35

Время шло, и теперь у Apple есть лучший способ настроить одиночные игры с помощью GCD ... так что другие могут захотеть это найти. Конечно, если вам все равно нужно поддерживать вещи до iOS4 ...

Kendall Helmstetter Gelner 22.06.2011 21:53

@KendallHelmstetterGelner, вы имеете в виду dispatch_once?

i_am_jorf 07.03.2013 01:49

Да, точно, dispatch_once. Это то, что я использую почти все время, если мне нужен синглтон.

Kendall Helmstetter Gelner 07.03.2013 01:53

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

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

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

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

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

amok 30.08.2010 01:09

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

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

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

Steve Madsen 22.04.2010 18:37

Хороший ответ - семейство OSAtomic - это хорошая вещь, о которой нужно знать

Bill 27.02.2011 04:24

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

matm 01.09.2011 20:39

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

Louis Gerbarg 02.09.2011 07:57

Хм, а зачем здесь нужна квалификация volatile? Поскольку sharedInstance инициализируется только один раз, почему мы не позволяем компилятору кэшировать его в регистре с помощью volatile?

Tony 29.12.2011 21:37

@Tony немного опоздал с ответом, но OSAtomicCompareAndSwapPtrBarrier требует volatile. Возможно, ключевое слово volatile состоит в том, чтобы не дать компилятору оптимизировать проверку? См .: stackoverflow.com/a/5334727/449161 и developer.apple.com/library/mac/#documentation/Darwin/Refere‌ nce /…

Ben Flynn 16.11.2012 22:23

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

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

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

Steve Madsen 22.04.2010 19:04

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

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

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

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

Синглтон.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 17.04.2012 12:04

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

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

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

Само собой разумеется, что делает существует в области класса. Он относится к классу, а не к экземпляру класса. Классы (в основном) являются объектами первого класса.

schwa 25.01.2011 21:30

Почему вы помещаете @synchroninzed ВНУТРИ метода?

user4951 04.05.2011 06:06

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

jscs 27.09.2011 06:27

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.

quellish 08.02.2012 23:50

Обновлено: эта реализация устарела с 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.)

Я бы просто жил попроще и вообще избегал инициализации.

Tom Andersen 17.05.2012 17:24

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

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

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

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

Как насчет

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

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

i_am_jorf 07.03.2013 01:34

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

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

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

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

[[Librarian sharedInstance] openLibrary]

к:

[Librarian openLibrary]

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

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

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

i_am_jorf 07.03.2013 01:14

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

KLSingleton is:

  1. Subclassible (to the n-th degree)
  2. ARC compatible
  3. Safe with alloc and init
  4. Loaded lazily
  5. Thread-safe
  6. Lock-free (uses +initialize, not @synchronize)
  7. Macro-free
  8. Swizzle-free
  9. Simple

KLSingleton

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

Oleg Trakhman 03.07.2012 22:28

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

kevinlawler 07.07.2012 10:19

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

kevinlawler 07.07.2012 10:24

NSLog (@ "инициализировать:% @", NSStringFromClass ([собственный класс]));

Oleg Trakhman 07.07.2012 14:24

Возможно, вы захотите, чтобы он был совместим с IB. Мой: stackoverflow.com/questions/4609609/…

Dan Rosenstark 05.09.2012 23:55

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

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 07.03.2013 01:12

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

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

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

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

    return sharedResourcesObj;
}

Другие вопросы по теме