it-swarm.com.ru

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

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

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

334
schwa

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

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

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

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}
207
Robbie Hanson
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

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

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

    return sharedSingleton;
  }
}

@end

[Источник]

95
Ben Hoffstein

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

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

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

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

58
Louis Gerbarg
 static MyClass * sharedInst = nil; 
 
 + (id) sharedInstance 
 {
 @synchronize (self) {
 if (sharedInst == nil) {
/* sharedInst настроен в init */
 [[self alloc] init]; 
} 
} 
 return sharedInst; 
} 
 
 - (id) init 
 {
 if (sharedInst! = nil) {
 [ Повышение NSException: NSInternalInconsistencyException 
 Формат: @ "[% @% @] не может быть вызван; используйте + [% @% @] вместо"], 
 NSStringFromClass ([self class]), NSStringFromSelector ( _cmd), 
 NSStringFromClass ([self class]), 
 NSStringFromSelector (@selector (sharedInstance) "]; 
} иначе if (self = [super init]) {
 sharedInst = self; 
/* Независимо от класса, определенного здесь */
} 
 возвращают sharedInst; 
} 
 
/* Они, вероятно, ничего не делают в 
 Приложении GC. синглтон 
 как фактический синглтон в 
 non CG приложении 
 */
 - (NSUInteger) retainCount 
 {
 return NSUIntegerMax ; 
} 
 
 - (одностороннее недействительное) освобождение 
 {
} 
 
 - (id) сохранить 
 {
 return sharedInst; 
} 
 
 - (id) autorelease 
 {
 return sharedInst; 
} 
14
Michael Nickerson

Редактировать: эта реализация устарела с 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.)

12
lorean

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

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

10
Matthieu Cormier

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

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

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

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

6
quellish

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

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

5
obscenum

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

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

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

@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
lajos

Как насчет

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

2
Tony

Для подробного обсуждения шаблона синглтона в Objective-C, посмотрите здесь:

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

2
Fred McCann

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

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

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

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

2
CJ Hanson

KLSingleton это:

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

KLSingleton

1
kevinlawler

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

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

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

[[Librarian sharedInstance] openLibrary]

чтобы:

[Librarian openLibrary]

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

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

0
chunkyguy

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

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

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

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.

0
TienDC
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
user370199

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

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

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

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

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

    return sharedResourcesObj;
}
0
Zolt