Вопрос

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

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

Это было полезно?

Решение

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

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

Итак, вы можете сделать что-то вроде этого:

static MySingleton *sharedSingleton;

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

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

@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

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

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

    return sharedSingleton;
  }
}

@end

[Источник]

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

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

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

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

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

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

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

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

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

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

Редактировать:Эта реализация устарела с появлением ARC.Пожалуйста, взгляните на Как реализовать синглтон Objective-C, совместимый с ARC? для корректной реализации.

Все реализации инициализации, которые я читал в других ответах, имеют общую ошибку.

+ (void) initialize {
  _instance = [[MySingletonClass alloc] init] // <----- Wrong!
}

+ (void) initialize {
  if (self == [MySingletonClass class]){ // <----- Correct!
      _instance = [[MySingletonClass alloc] init] 
  }
}

В документации Apple рекомендуется проверить тип класса в блоке инициализации.Потому что подклассы по умолчанию вызывают инициализацию.Существует неочевидный случай, когда подклассы могут быть созданы косвенно через KVO.Если вы добавите следующую строку в другой класс:

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]

Objective-C неявно создаст подкласс MySingletonClass, что приведет к второму запуску +initialize.

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

- (id) init { <----- Wrong!
   if (_instance != nil) {
      // Some hack
   }
   else {
      // Do stuff
   }
  return self;
}

Но ты выстрелишь себе в ногу;или, что еще хуже, дать другому разработчику возможность выстрелить себе в ногу.

- (id) init { <----- Correct!
   NSAssert(_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self){
      // Do stuff
   }
   return self;
}

TL;DR, вот моя реализация

@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
   if (self == [MySingletonClass class]){
      _instance = [[MySingletonClass alloc] init];
   }
}

- (id) init {
   ZAssert (_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self) {
      // Initialization
   }
   return self;
}

+ (id) getInstance {
   return _instance;
}
@end

(Замените ZAssert на наш собственный макрос утверждения;или просто NSAssert.)

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

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

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

// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;

// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
    return (MySingleton *)sharedInstance;
}

+ (MySingleton*)sharedInstance
{
    @synchronized(self)
    {
        if (sharedInstance == nil)
        {
            sharedInstance = [[MySingleton alloc] init];
            // Replace expensive thread-safe method 
            // with the simpler one that just returns the allocated instance.
            SEL origSel = @selector(sharedInstance);
            SEL newSel = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, origSel);
            Method newMethod = class_getClassMethod(self, newSel);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

Короткий ответ:Поразительнй.

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

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

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

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

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

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

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

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

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

Синглтон.м:

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

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

@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

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

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

Вот макрос что я собрал:

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

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

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

Как насчет

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

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

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

КЛСинглтон – это:

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

КЛСинглтон

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

Просто хотел оставить это здесь, чтобы не потерять.Преимущество этого метода в том, что его можно использовать в InterfaceBuilder, что является ОГРОМНЫМ преимуществом. Это взято из другого вопроса, который я задал:

static Server *instance;

+ (Server *)instance { return instance; }

+ (id)hiddenAlloc
{
    return [super alloc];
}

+ (id)alloc
{
    return [[self instance] retain];
}


+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        instance = [[Server hiddenAlloc] init];
    }
}

- (id) init
{
    if (instance)
        return self;
    self = [super init];
    if (self != nil) {
        // whatever
    }
    return self;
}
static mySingleton *obj=nil;

@implementation mySingleton

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

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

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

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

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

- (id)autorelease {
    return self;
}

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

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

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

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

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

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

[[Librarian sharedInstance] openLibrary]

к:

[Librarian openLibrary]

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

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

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

Мой путь прост:

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 не будет введен.Вторая проверка if(!initialized) заключается в том, чтобы убедиться, что он еще не инициализирован, когда текущий поток получает LOCK.

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

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

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

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

    return sharedResourcesObj;
}

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

Однако иногда мне нужно поместить синглтон в NIB, и в этом случае я использую следующее:

@implementation Singleton

static Singleton *singleton = nil;

- (id)init {
    static BOOL initialized = NO;
    if (!initialized) {
        self = [super init];
        singleton = self;
        initialized = YES;
    }
    return self;
}

+ (id)allocWithZone:(NSZone*)zone {
    @synchronized (self) {
        if (!singleton)
            singleton = [super allocWithZone:zone];     
    }
    return singleton;
}

+ (Singleton*)sharedSingleton {
    if (!singleton)
        [[Singleton alloc] init];
    return singleton;
}

@end

Я оставляю реализацию -retain (и т. д.) для читателя, хотя приведенный выше код — это все, что вам нужно в среде со сборкой мусора.

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

+ (MySingleton*)sharedInstance
{
    @synchronized(self)  <-------- self does not exist at class scope
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

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

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

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

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top