Поточно-безопасное создание синглтона

Какой метод синхронизации следует использовать, чтобы одноэлементный объект оставался одноэлементным?

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

или с помощью мьютекса?

#import <pthread.h>

static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;

+(Foo*)sharedInstance
{
   pthread_mutex_lock(&_mutex);
   if (nil == _sharedInstance)
   {
      _sharedInstance = [[Foo alloc] init];
      ...
   }
   pthread_mutex_unlock(&_mutex);
   return _sharedInstance;
}

Хммм .. есть комментарии по этому поводу?


person MacTouch    schedule 04.02.2010    source источник
comment
Возможно, вам будет интересно прочитать это (steve.yegge.googlepages.com/singleton-considered- глупо).   -  person sand    schedule 04.02.2010
comment
Несмотря на ненависть Йегге к синглетонам, они определенно служат определенной цели на iPhone. Но если вы просто создаете «пространство имен», используйте вместо этого методы класса.   -  person bentford    schedule 15.07.2011
comment
@bentford - Я программирую айфоны уже 4 года, старый код и новый. Я видел, как синглтоны использовались однажды (в довольно паршивом коде).   -  person Hot Licks    schedule 03.01.2014
comment
@HotLicks Я использую 1 или 2 синглтона почти во всех своих проектах. Два варианта использования: 1) обработка длительных сетевых операций и 2) управление глобальным постоянным контекстом для CoreData.   -  person bentford    schedule 10.01.2014


Ответы (5)


Самый быстрый потокобезопасный способ сделать это - использовать Grand Central Dispatch (libdispatch) и dispatch_once ().

+(MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        sharedInstance = [[MyClass alloc] init];
    });

    return sharedInstance;
}
person Colin Wheeler    schedule 04.02.2010
comment
зачем вам использовать dispatch_once? Почему вы не можете просто выполнить оператор if, подобный этому: if (sharedInstance == nil) {sharedInstance = [MyClass alloc] init]; - person Honey; 06.04.2016
comment
это не гарантирует, что он будет выделен только один раз. dispatch once гарантирует, что блок будет выполнен только один раз, даже если несколько потоков одновременно вызывают один и тот же код. В вашем коде один поток может выполнить оператор if, затем перейти к выделению экземпляра, затем другой поток одновременно оценивает оператор if, а затем выделяет второй экземпляр, и теперь у вас есть утечка памяти, которую вы не можете восстановить . Ваш код работает, но он не будет обрабатывать случаи, подобные тем, которые я только что описал. - person Colin Wheeler; 07.04.2016
comment
Спасибо, я понял вашу точку зрения о потокобезопасности. Чего я не понимаю, так это почему просто недостаточно использовать dispatch_once? Почему я до сих пор вижу, как люди делают оператор if + dispatch_once + статический + метод класса? Если вы используете dispatch_once, то почему вы указываете static? - person Honey; 07.04.2016
comment
stackoverflow.com/questions/4576607 / Я не использовал оператор if, потому что dispatch_once выполняется только один раз, когда в нем нет необходимости. static гарантирует, что переменная будет существовать при нескольких выполнениях метода, в противном случае при втором выполнении этого метода мы получим новый указатель, который не указывает на выделенную память, которую мы сделали в первый раз, и эта память будет утекать. Метод класса обычно служит для демонстрации того, что вы имеете дело с общим экземпляром, например NSFileManager.defaultManager () - person Colin Wheeler; 20.04.2016

Если кому-то интересно, вот макрос для того же:

   /*!
    * @function Singleton GCD Macro
    */
    #ifndef SINGLETON_GCD
    #define SINGLETON_GCD(classname)                            \
                                                                \
    + (classname *)shared##classname {                          \
                                                                \
        static dispatch_once_t pred;                            \
        static classname * shared##classname = nil;             \
        dispatch_once( &pred, ^{                                \
            shared##classname = [[self alloc] init];            \
        });                                                     \
        return shared##classname;                               \
    }                                                           
    #endif
person Arvin    schedule 18.11.2011

Эта страница CocoaDev может быть полезна для ваших нужд.

person Laurent Etiemble    schedule 04.02.2010
comment
Спасибо. Хм .. Я не думал о возможности сохранить или перевыпустить синглтон. Черт, кто такие вещи делает? :) - person MacTouch; 04.02.2010

Если кому-то интересно, вот еще один макрос для того же самого :)

IMHO, он обеспечивает большую гибкость по сравнению с другими вариантами.

#define SHARED_INSTANCE(...) ({\
    static dispatch_once_t pred;\
    static id sharedObject;\
    dispatch_once(&pred, ^{\
        sharedObject = (__VA_ARGS__);\
    });\
    sharedObject;\
})

Использование, однострочная инициализация:

+ (instancetype) sharedInstanceOneLine {
    return SHARED_INSTANCE( [[self alloc] init] );
}

Использование, многострочная инициализация (обратите внимание на фигурные скобки вокруг блока кода):

+ (instancetype) sharedInstanceMultiLine {
    return SHARED_INSTANCE({
        NSLog(@"creating shared instance");
        CGFloat someValue = 84 / 2.0f;
        [[self alloc] initWithSomeValue:someValue]; // no return statement
    });
}

Использование в правой части задания:

- (void) someMethod {
    MethodPrivateHelper *helper = SHARED_INSTANCE( [[MethodPrivateHelper alloc] init] );
    // do smth with the helper
}
// someMethod should not call itself to avoid deadlock, see bbum's answer

Эта модификация использует две языковые функции: составные выражения GCC. расширение, которое также поддерживается Clang, и поддержка макросов C99 .

После предварительной обработки результат будет выглядеть так (вы можете проверить это самостоятельно, вызвав Product > Perform Action > Preprocess "YourClassName.m" в Xcode 5):

+ (instancetype) sharedInstanceOneLine {
    return ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ( [[self alloc] init] );
        });
        sharedObject; // this object will be returned from the block
    });
}

+ (instancetype) sharedInstanceMultiLine {
    return ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ({
                NSLog(@"creating shared instance");
                CGFloat someValue = 84 / 2.0f;
                [[self alloc] initWithSomeValue:someValue];
            });
        });
        sharedObject;
    });
}

- (void) someMethod {
    MethodPrivateHelper *helper = ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ( [[MethodPrivateHelper alloc] init] );
        });
        sharedObject;
    });
}
person skozin    schedule 08.01.2014

person    schedule
comment
Спасибо за это, хотя мне потребовалось полчаса, чтобы найти правильную ссылку на синтаксис блоков (^) developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/, который по какой-то причине не включен в их документацию по Objective C. - person Harald Scheirich; 05.02.2010
comment
Спасибо. Это было первое, о чем я подумал, увидев твой ответ. Я не знал, что есть возможность писать такие блоки в объекте c (например, реализация анонимного класса в Java или делегаты в C #). Это ответ на мою другую проблему здесь: stackoverflow.com / questions / 2118728 / Вместо обратных вызовов я теперь использую блоки. :) Возможно, я инициализирую свой синглтон в AppDelegate и на самом деле не забочусь о параллелизме потоков. Там простой экземпляр, когда нужно. Это своего рода кеш для некоторых вещей, таких как изображения и т. Д. - person MacTouch; 05.02.2010
comment
@bbum, (1) зачем -init вызывать +sharedInstance, и (2) почему вы включаете строку if (sharedInstance) return sharedInstance;? - person ma11hew28; 10.07.2011
comment
Просто для полноты - вышеупомянутое работает на iOS 4 и выше, когда блоки были введены в iOS. - person Hunter; 17.07.2011
comment
Я бы предложил удалить if (sharedInstance) return sharedInstance, это блокировка с двойной проверкой и не работает (см. aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf) - person Jochen; 29.11.2011
comment
.... и на самом деле это все равно вызовет тупик, если -init вызовет + sharedInstance, ' - person bbum; 29.11.2011
comment
@bbum, так что следует ли нам просто использовать решение @ColinWheeler и просто убедиться, что -init не вызывает +sharedInstance? - person ma11hew28; 05.12.2011
comment
@MattDiPasquale В значительной степени. Я все еще думаю об этом; Я полагаю, что условие для отправки может помочь, но на этом пути лежит потенциальное безумие. - person bbum; 05.12.2011
comment
Почему вы отредактировали свой ответ, чтобы удалить единственную информацию, которая сделала его ценным? Ваш отредактированный код ничем не отличается от исходного ответа Колина, и теперь ваш вклад не имеет смысла, ваш код больше не соответствует вашему тексту (где разделение между alloc и init, о котором весь ваш ответ?). Если запоздало вы считаете, что ваш ответ недействителен, то (1) добавьте примечание об этом, вместо того чтобы сделать ваш ответ непоследовательным, и (2) объясните, почему он был бы недействительным. Спасибо! - person KPM; 20.09.2012
comment
@KPM Вы абсолютно правы. Смотрите обновление. Честно говоря, у меня нет хорошего чистого решения этой проблемы. Проблема в том, что sharedInstance должен быть как эксклюзивным, так и повторно участвующим, что полностью противоречит самому себе. мышление - person bbum; 20.09.2012
comment
Хорошо спасибо! Но если init вызывает sharedInstance, мы повторно вводим метод класса. Следовательно, dispatch_once не сработает, а sharedInstance просто вернет выделенную, но неинициализированную статическую переменную. Затем, когда init возвращается, статическая переменная назначается инициализированному экземпляру. Правильно? Зачем нам нужен повторный вход? - person KPM; 20.09.2012
comment
Поскольку dispatch_once () еще не вернулся, он блокируется при второй записи. Весь смысл dispatch_once () в том, что вы можете инициализировать статический объект ровно один раз, и, пока он не будет инициализирован, ничто другое не сможет выполнить код после dispatch_once (). По крайней мере, прослеживание тупика делает очевидным то, что произошло. - person bbum; 20.09.2012
comment
@bbum Не могли бы вы посмотреть здесь: stackoverflow.com/questions/20895214/ ваш ответ создает совершенно новую дискуссию о разделении alloc и init - некоторые люди заинтересованы подробное объяснение - person Oleksandr Karaberov; 03.01.2014