Як повинен виглядати мій однотонний Objective-C? [зачинено]


334

Мій метод однодольних аксесуарів, як правило, є деяким варіантом:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Що я міг би зробити, щоб покращити це?


27
У вас є чудово, хоча ви можете перенести декларацію глобальної змінної у метод + екземпляр (єдине місце, яке воно потрібно використовувати, якщо ви не дозволяєте також її встановити) та використовувати ім’я типу + defaultMyClass або + sharedMyClass для вашого методу. + екземпляр не виявляє наміру.
Кріс Хансон,

Оскільки навряд чи «відповідь» на це питання незабаром зміниться, я розміщую історичний замок на цьому питанні. Дві причини 1) Багато поглядів, голосів та хорошого змісту 2) Запобігання йо-йогінгу відкритого / закритого. Це було чудовим питанням для свого часу, але питання таких типів не підходять для переповнення стека. Зараз у нас є огляд коду для перевірки робочого коду. Будь ласка, будьте всі обговорення цього питання на це мета-питання .
Джордж Стокер

Відповіді:


207

Інший варіант - використовувати +(void)initializeметод. З документації:

Час виконання надсилає initializeкожному класу в програмі рівно один раз безпосередньо перед класом або будь-яким класом, який успадковує його, надсилається перше повідомлення з програми. (Таким чином, метод ніколи не може бути застосований, якщо клас не використовується.) Під час виконання програма передає initializeповідомлення класам безпечним способом. Суперкласи отримують це повідомлення перед своїми підкласами.

Тож ви можете зробити щось подібне до цього:

static MySingleton *sharedSingleton;

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

7
Якщо час виконання буде колись викликати це лише один раз, що робити BOOL? Це застереження у випадку, якщо хтось викликає цю функцію явно зі свого коду?
Aftermathew

5
Так, це застереження, оскільки функцію можна також викликати безпосередньо.
Роббі Гансон

33
Це також потрібно, оскільки можуть бути підкласи. Якщо вони не перекриють, +initializeїх реалізація надкласових класів буде викликана, якщо вперше використовується підклас.
Свен

3
@Paul ви можете змінити releaseметод і зробити його порожнім. :)

4
@aryaxt: З перелічених документів це вже безпечно для потоків. Отже, дзвінок проводиться один раз за час виконання. Це, здається, є правильним, безпечним для потоків оптимальним рішенням.
lilbyrdie

95
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

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

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

    return sharedSingleton;
  }
}

@end

[Джерело]


7
Це все, що зазвичай слід використовувати для одиночних. Крім усього іншого, зберігання класів окремо миттєвим полегшує їх тестування, оскільки ви можете перевірити окремі екземпляри, а не мати змогу скинути їх стан.
Кріс Хансон

3
Стиг Браутасет: Ні, не нормально залишати @synchronized у цьому прикладі. Саме там можна обробляти можливий стан перегонів двох потоків, що виконують цю статичну функцію одночасно, обидва проходять тест "if (! SharedSingleton)" одночасно, і таким чином призводять до двох [MySingleton alloc] s. .. @ синхронізований {блок блоків} змушує цей гіпотетичний другий потік чекати, коли перший потік вийде з {блок блоку}, перш ніж йому буде дозволено продовжувати його. Я сподіваюся, що це допомагає! =)
МехЕтан

3
Що заважає комусь все-таки зробити власний екземпляр об’єкта? MySingleton *s = [[MySingelton alloc] init];
линдонська лисиця

1
@lindonfox Яка відповідь на ваше запитання?
Раффі Хатчадуріан

1
@Raffi - вибачте, я думаю, що, мабуть, забув вставити свою відповідь. У всякому разі, я дістав книгу, Pro Objective-C Design Patterns for iOSі в ній написано, як ви робите "суворий" сингелтон. В основному, оскільки ви не можете зробити способи ініціації приватними, вам потрібно змінити методи alloc і копіювати. Тож якщо ви спробуєте зробити щось подібне, [[MySingelton alloc] init]ви отримаєте помилку часу виконання (хоча, на жаль, не помилка часу компіляції). Я не розумію, як усі подробиці створення об'єкта, але ви реалізуєте, + (id) allocWithZone:(NSZone *)zoneщо викликаєтьсяsharedSingleton
линдон лис

59

Відповідно до моєї іншої відповіді нижче, я думаю, ви повинні робити:

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

6
Не турбуйтеся з усім, що ви робите вище. Зробіть свої (сподіваюся, надзвичайно мало) синглтонами окремо-миттєвими, і просто використовуйте спільний / типовий метод. Те, що ви зробили, необхідне лише в тому випадку, якщо ви справді, справді, ТІЛЬКИ хочете отримати єдиний екземпляр свого класу. Чого ти не робиш, особливо. для одиничних випробувань.
Кріс Гансон,

Річ у тому, що це зразок коду Apple для "створення сингтона". Але так, ви абсолютно праві.
Колін Барретт

1
Зразок коду Apple є правильним, якщо ви хочете "справжнього" синглтона (тобто об'єкта, який можна інстанціювати лише один раз, коли-небудь), але, як каже Кріс, це рідко є те, що ви хочете або потрібно, тоді як якийсь встановлений екземпляр, який можна встановити, - це те, що ви зазвичай хочуть.
Люк Редпат

Ось макрос для описаного вище способу: gist.github.com/1057420 . Це те, що я використовую.
Кобський

1
Відміни тестів осторонь, проти цього рішення нічого не йдеться, правда? І це швидко і безпечно.
LearnCocos2D

58

Оскільки Кендалл опублікував безпечний сингл-потік, який намагається уникнути блокування витрат, я подумав, що також підкину:

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

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

Добре, дозвольте мені пояснити, як це працює:

  1. Швидкий випадок: у звичайному виконанні sharedInstanceвже встановлено, тому whileцикл ніколи не виконується і функція повертається після простого тестування на існування змінної;

  2. Уповільнений регістр: якщо sharedInstanceйого немає, то примірник виділяється та копіюється в нього за допомогою Порівняння та заміни ("CAS");

  3. Змістовий випадок: Якщо обидва потоки намагаються викликати sharedInstanceодночасно І одночасно sharedInstanceне існує, вони обидва ініціалізують нові екземпляри синглтона та намагаються перенести його на позицію CAS. Незалежно від того, хто виграє, CAS повертається негайно, той, хто програє, звільняє екземпляр, який він тільки що виділив, і повертає (тепер встановлений) sharedInstance. Сингл OSAtomicCompareAndSwapPtrBarrierвиступає як бар'єр запису для потоку налаштування, так і бар'єр читання з тестової нитки.


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

Приємна відповідь - сім'я OSAtomic - це добре знати,
Білл

1
@Louis: Дивовижна, справді освічуюча відповідь! Хоча одне питання: що мені initробити у вашому підході? sharedInstanceМені здається, що викидання виключення при ініціалізації не є хорошою ідеєю. Що робити тоді, щоб запобігти виклику користувачів initбагато разів?
математика

2
Я взагалі не заважаю. Часто є поважні причини, які дозволяють дозволити те, що, як правило, однократне, множиться по інстанції, найчастіше це стосується певних типів одиничного тестування. Якби я дійсно хотів застосувати один екземпляр, я б, ймовірно, перевірив метод init, щоб перевірити, чи існує глобальний, і якщо він був, я маю його звільнити самоврядування та повернути глобальний.
Луї Гербарг

1
@Tony трохи запізнюється з відповіддю, але OSAtomicCompareAndSwapPtrBarrier вимагає змін. Можливо, мінливе ключове слово полягає в тому, щоб запобігти оптимізації компілятора від перевірки? Дивіться: stackoverflow.com/a/5334727/449161 та developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Бен Флінн

14
статичний MyClass * sharedInst = нуль;

+ (id) sharedInstance
{
    @synchronize (само) {
        якщо (sharedInst == нуль) {
            / * sharedInst налаштовано в init * /
            [[самоалок] ініт];
        }
    }
    повернути sharedInst;
}

- (ід) ініт
{
    якщо (sharedInst! = нуль) {
        [NSException підвищення: NSInternalInconsistencyException
            формат: @ "[% @% @] не можна викликати; використовуйте + [% @% @] замість"],
            NSStringFromClass ([самоклас]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([самоклас]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [super init]) {
        sharedInst = Я;
        / * Який би клас не був тут * /
    }
    повернути sharedInst;
}

/ * Вони, ймовірно, нічого не роблять
   додаток GC Тримає одиноку
   як фактичний синглтон в a
   не додаток CG
* /
- (NSUInteger) retainCount
{
    повернути NSUIntegerMax;
}

- (oneway void) випуск
{
}

- (ід) зберігати
{
    повернути sharedInst;
}

- (ід) автореліз
{
    повернути sharedInst;
}

3
Я помітив, що Кланг скаржиться на витік, якщо ви не призначите результат [[self alloc] init]до спільного Інсту.
pix0r

Підрив такої init - це досить некрасивий підхід ІМО. Не возиться з init та / або фактичним створенням об'єкта. Якщо ви замість цього звертаєтесь до контрольованої точки доступу до спільного екземпляра, не вкладаючи одноосібно в об'єкт, пізніше ви отримаєте щасливіший час, якщо писати тести і т. Д. Жорсткі сингтони занадто сильно використовуються.
окулус

12

Редагувати: Ця реалізація застаріла з ARC. Будь ласка, подивіться, як я можу реалізувати одиночний об'єктив Objective-C, сумісний з ARC? для правильної реалізації.

Усі реалізації ініціалізації, які я читав в інших відповідях, мають загальну помилку.

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

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

Документація Apple рекомендує перевірити тип класу у блоці ініціалізації. Оскільки підкласи викликають ініціалізацію за замовчуванням. Існує неочевидний випадок, коли підкласи можуть створюватися опосередковано через KVO. Бо якщо ви додасте наступний рядок в інший клас:

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

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

Ви можете подумати, що вам слід неявно перевірити наявність ініціалізації дублікатів у вашому блоці init:

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

Але ти будеш стріляти собі в ногу; або ще гірше - дати іншому розробникові можливість стріляти собі в ногу.

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

TL; DR, ось моя реалізація

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

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

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

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


1
Я просто жив би простіше і уникав ініціалізації взагалі.
Том Андерсен


9

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

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

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

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

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

Це той самий ефект, якщо використовувати exchangeImplementations означає, що після init, коли ви викликаєте sharedInstance, ви дійсно викликаєте simpleSharedInstance. Я насправді почав із заміни методу, але вирішив, що краще просто переключити реалізацію навколо, щоб оригінал все ще існував, якщо потрібно ...
Kendall Helmstetter Gelner,

Під час подальшого тестування я не міг змусити замінитиМетод на роботу - у повторних викликах код все-таки називався оригінальним sharedInstance замість simpleSharedInstance. Я думаю, це може бути тому, що вони обидва методи рівня класу ... Я використовував замість: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); і деякі їх варіанти. Я можу перевірити код, який я опублікував, і простоSharedInstance викликається після першого проходу через sharedInstance.
Kendall Helmstetter Gelner

Ви можете зробити безпечну версію для потоку, яка не оплачує витрати на блокування після ініціалізації, не роблячи купу вимкнення виконання, я опублікував реалізацію нижче.
Луї Гербарг

1
+1 відмінна ідея. Я просто люблю ті речі, які можна зробити під час виконання. Але в більшості випадків це, мабуть, передчасна оптимізація. Якби мені справді довелося позбутися вартості синхронізації, я, мабуть, скористався блюкізною версією Луї.
Свен

6

Коротка відповідь: Казкове.

Довга відповідь: Щось на кшталт…

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

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

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

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

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

Не забудьте прочитати заголовок dispatch / Once.h, щоб зрозуміти, що відбувається. У цьому випадку коментарі до заголовка більше застосовні, ніж сторінки документів або man.


5

Я переконував синглтон в клас, тому інші класи можуть успадковувати властивості сингтона.

Singleton.h:

static id sharedInstance = nil;

#define DEFINE_SHARED_INSTANCE + (id) sharedInstance {  return [self sharedInstance:&sharedInstance]; } \
                               + (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }

@interface Singleton : NSObject {

}

+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;

@end

Singleton.m:

#import "Singleton.h"


@implementation Singleton


+ (id) sharedInstance { 
    return [self sharedInstance:&sharedInstance];
}

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

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

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

- (id)retain {
    return self;
}

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

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}


@end

Ось приклад якогось класу, який ви хочете стати одиноким.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

Єдине обмеження щодо класу Singleton, це те, що це підклас NSObject. Але більшість часу я використовую одиночні кнопки у своєму коді, вони насправді є підкласами NSObject, тому цей клас дуже полегшує моє життя та робить код чистішим.


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

2

Це працює і в не зібраному сміттям середовищі.

@interface MySingleton : NSObject {
}

+(MySingleton *)sharedManager;

@end


@implementation MySingleton

static MySingleton *sharedMySingleton = nil;

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


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


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

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


-(id)retain {
    return self;
}


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


-(void)release {
    //do nothing    
}


-(id)autorelease {
    return self;    
}


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

    //initialize here

    return self;
}

@end

2

Чи не повинен це бути безпечним для ниток і уникати дорогого блокування після першого дзвінка?

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

2
Метод подвійного перевірки блокування, який використовується тут, часто є справжньою проблемою в деяких середовищах (див. Aristeia.com/Papers/DDJ_Jul_Aug_2004_revid.pdf або Google it). Поки не буде показано інше, я б припустив, що Objective-C не застрахований. Також см wincent.com/a/knowledge-base/archives/2006/01 / ... .
Стів Мадсен

2

Ось макрос, який я зібрав:

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

Він заснований на роботі Метта Галлахера. Але зміна впровадження для використання методу шиплячих, як описано тут Дейвом Маклакланом з Google .

Я вітаю коментарі / внески.


посилання здається порушеним - де я можу взяти це джерело?
амок

2

Як щодо

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Так ви уникаєте вартості синхронізації після ініціалізації?


Дивіться обговорення подвійного перевіреного блокування в інших відповідях.
i_am_jorf


1

KLSingleton:

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

KLSingleton


1
Я використовую ваш NSSingleton для свого проекту, і він здається несумісним з KVO. Справа в тому, що KVO створює підклас для кожного об'єкта KVO з префіксом NSKVONotifying_ MyClass . І це змушує MyClass + ініціалізувати та -init методи викликати двічі.
Олег Трахман

Я перевірив це на останньому Xcode і не мав проблем із реєстрацією або отриманням подій KVO. Ви можете підтвердити це за допомогою наступного коду: gist.github.com/3065038 Як я вже згадував у Twitter, методи + ініціалізації викликаються один раз для NSSingleton та один раз для кожного підкласу. Це властивість Objective-C.
kevinlawler

Якщо ви додасте NSLog(@"initialize: %@", NSStringFromClass([self class]));в +initializeметод, ви можете перевірити, що класи ініціалізуються лише один раз.
kevinlawler

NSLog (@ "ініціалізувати:% @", NSStringFromClass ([самоклас]));
Олег Трахман

Можливо, ви також хочете, щоб він був сумісний із IB Моє: stackoverflow.com/questions/4609609/…
Dan Rosenstark

0

Ви не хочете синхронізувати самостійно ... Оскільки власний об'єкт ще не існує! Ви закінчуєте блокування тимчасового значення id. Ви хочете переконатися, що ніхто інший не може запускати методи класу (sharedInstance, alloc, allocWithZone: тощо), тому вам потрібно синхронізувати об'єкт класу:

@implementation MYSingleton

static MYSingleton * sharedInstance = nil;

+( id )sharedInstance {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ [ MYSingleton alloc ] init ];
    }

    return sharedInstance;
}

+( id )allocWithZone:( NSZone * )zone {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ super allocWithZone:zone ];
    }

    return sharedInstance;
}

-( id )init {
    @synchronized( [ MYSingleton class ] ) {
        self = [ super init ];
        if( self != nil ) {
            // Insert initialization code here
        }

        return self;
    }
}

@end

1
Решта методів, методів аксесуарів, мутаторних методів тощо повинні синхронізуватися самостійно. Усі методи класу (+) та ініціалізатори (і, можливо, -dealloc) повинні синхронізуватися на об’єкті класу. Ви можете уникнути необхідності синхронізації вручну, якщо використовуватимете властивості Objective-C 2.0 замість методів accessor / mutator. Усі object.property та object.property = foo, автоматично синхронізуються до себе.
Роб Дотсон

3
Поясніть, будь ласка, чому ви вважаєте, що selfоб'єкт не існує в методі класу. Час виконання визначає, яку реалізацію методу потрібно викликати, виходячи з того самого значення, яке воно надає, як і selfдля кожного методу (класу чи екземпляра).
dreamlax

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

0

Просто хотів залишити це тут, щоб я не втратив цього. Перевага цього полягає в тому, що він може бути використаний в InterfaceBuilder, що є ВЕЛИЧЕЗНОЮ перевагою. Це взято з іншого питання, яке я задав :

static Server *instance;

+ (Server *)instance { return instance; }

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

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


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

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

0
static mySingleton *obj=nil;

@implementation mySingleton

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

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

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

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

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

- (id)autorelease {
    return self;
}

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

0

Я знаю, що є багато коментарів до цього "питання", але я не бачу багатьох людей, які пропонують використовувати макрос для визначення сингтона. Це така загальна закономірність, і макрос значно спрощує синглтон.

Ось макроси, які я написав на основі декількох реалізацій Objc, які я бачив.

Singeton.h

/**
 @abstract  Helps define the interface of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the implementation.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;


/**
 @abstract  Helps define the implementation of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the interface.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
    static BOOL initialized = NO; \
    if(!initialized) \
    { \
        initialized = YES; \
        __ ## NAME = [[TYPE alloc] init]; \
    } \
} \
\
\
+ (TYPE *)NAME \
{ \
    return __ ## NAME; \
}

Приклад використання:

MyManager.h

@interface MyManager

SingletonInterface(MyManager, sharedManager);

// ...

@end

MyManager.m

@implementation MyManager

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
    }

    return self;
}

SingletonImplementation(MyManager, sharedManager);

// ...

@end

Чому макрос інтерфейсу, коли він майже порожній? Узгодженість коду між файлами заголовка та коду; ремонтопридатність, якщо ви хочете додати більше автоматичних методів або змінити їх.

Я використовую метод ініціалізації, щоб створити синглтон, який використовується в найпопулярнішій відповіді тут (під час написання).


0

За допомогою методів класу Objective C ми можемо просто уникати використання однотонного шаблону звичайним способом:

[[Librarian sharedInstance] openLibrary]

до:

[Librarian openLibrary]

загортаючи клас у інший клас, який просто має Методи класів , таким чином, немає шансів випадково створити повторювані екземпляри, оскільки ми не створюємо жодного екземпляра!

Більш детальний блог я написав тут :)


Ваше посилання більше не функціонує.
i_am_jorf

0

Щоб продовжити приклад від @ robbie-hanson ...

static MySingleton* sharedSingleton = nil;

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

- (id)init {
    self = [super init];
    if (self) {
        // Member initialization here.
    }
    return self;
}

0

Мій шлях такий простий:

static id instanceOfXXX = nil;

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

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

    return instanceOfXXX;
}

Якщо синхронізація вже ініціалізована, блок LOCK не буде введено. Друга перевірка, якщо (! Ініціалізовано), щоб переконатися, що вона ще не ініціалізована, коли поточний потік набуває ЗАМОКУ.


Не ясно, що маркування initializedяк volatileдостатньо. Див. Aristeia.com/Papers/DDJ_Jul_Aug_2004_revid.pdf .
i_am_jorf

0

Я не прочитав усі рішення, тож пробачте, якщо цей код є зайвим.

На мою думку, це найбільш безпечна реалізація.

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

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

    return sharedResourcesObj;
}

-4

Зазвичай я використовую код, приблизно такий, як у відповіді Бен Гофштейн (який я також вийшов із Вікіпедії). Я використовую це з причин, які висловив Кріс Гансон у своєму коментарі.

Однак іноді в мене виникає необхідність помістити одиночку в NIB, і в такому випадку я використовую наступне:

@implementation Singleton

static Singleton *singleton = nil;

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

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

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

@end

Я залишаю реалізацію -retain(тощо) читачеві, хоча вищезгаданий код - це все, що вам потрібно в середовищі, що збирається зі сміттям.


2
Ваш код не є безпечним для потоків. Він використовує синхронізований метод alloc, але не метод init. Перевірка ініціалізованого bool не є безпечною для потоків.
Mecki

-5

Прийнята відповідь, хоча і складається, невірна.

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

За документацію Apple:

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

Навіть якщо використання самостійної роботи не повинно, і це виглядає як помилка копіювання та вставки для мене. Правильною реалізацією для фабричного методу класу буде:

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

6
Само собою, безумовно , існує область його застосування. Він посилається на клас замість екземпляра класу. Класи - це (переважно) об'єкти першого класу.
schwa

Чому ви застосовуєте @synchroninzed ВІД методу?
user4951

1
Як уже говорило schwa, self це клас класу всередині класового методу. Дивіться мій коментар до фрагменту, який демонструє це.
jscs

selfіснує, але використовуючи його як ідентифікатор, переданий до @synchronized, синхронізуватиме доступ до методів екземпляра. Як вказує @ user490696, є випадки (наприклад, одиночні кнопки), коли краще використовувати об’єкт класу. Із посібника з програмування Obj-C:You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
примхливий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.